PEM with OpenSSL: Reading ASN.1[OpenSSL Home][Home]
We need ways to distribute our public keys, private keys and digital certificates in a portable format. One of the most common forms is Distinguished Encoding Rules (DER) encoding of ASN.1. Overall it is truly binary representation of the encoded data. For a signature we often have the form of (\(r,s\)), a public key (\(pk\)) and a message (\(M\)). To check the signature we take the message (\(M\)), \(r\), \(s\) and \(pk\) and valid the signature. A typical format for the representation of the signature is in a DER format. In this case we will read in a DER hex string, and then determine the values of \(r\) amd \(s\), along with other forms.
|
Theory
The most common digital signature is ECDSA (Elliptic Curve Digital Signature Algorithm). It takes a message (M), a private key (sk) and a random value (k) and produces a digital signature of r and s. These are just two integer values. To check the signature, we take the message (M), the associated public key (pk), r and s, and perform a validation test, and if it passes, the message has a correct signature. But what does the signature actually look like? Basically, for ECSAA, it us just two numbers \(r\) and \(s\) that we add to the message. In many cases, it takes the form of the DER format, and which uses ASN.1 to define abstract types and values. Basically, it takes the two integer values (r and s) and encapsulates them into a more structured format. This format can be read by any computer system. One of the most basic types is SEQUENCE and is an ordered collection of one or more types. In DER, SEQUENCE is identified with a tag of “30”, and followed by a byte value for the length of the object defined. The other common types are OBJECT IDENTIFIER (and which has a tag of “06”), a BIT STRING (and which has a tag of “03”) and INTEGER (and which has a tag of “02”). In the case of a signature, we just use the INTEGER definition for the values.
So here is an example DER signature from NIST P-192 (and which uses 192-bit integer values):
3035021900935f599bbdb30fc81a8b9de2f82311c6fa704838b53f9d7a0218267e3abb5bc3a44b0e368442ed3699b23ce87a28bc32cc53
We first encounter the SEQUENCE (“30”), and then the next byte defines the length of the values which come next. In this case, “0x35” is 53 bytes. If you count the number of bytes after 35, you will find there are 53 bytes (or 106 hex characters):
30 35 02 19 00935f599bbdb30fc81a8b9de2f82311c6fa704838b53f9d7a 02 18 267e3abb5bc3a44b0e368442ed3699b23ce87a28bc32cc53
Next, we have a “02” tag, and then a “19”, and where the “19” value identifies 25 bytes (or 50 hex characters). We can then read r as the next 50 hex characters. Next, we have another “02” tag, and then a “19”, and where the “18” value identifies 24 bytes (or 48 hex characters). We can then read r as the next 48 hex characters. So, can we check the size of the integers produced? Well, we just multiply the number of bytes by 8, and we will determine this. Thus r is 25 bytes long, but the first byte is a zero, and s is 24 bytes, so the length of the values of r and s are 24 bytes long. This gives us 192 bits and which fits with the curve (P-192).
Now we will try a signature from a common curve (secp256k1) and which uses 256-bit values:
3046022100e4e87c417196c6e5cd63f93e94929ccda6d04fc0a7446922baf3070e854ec4f4022100a1ecd098008329de9bc93fb2ded6aaceecc921f7183d6b3cfc673b3ef8af219e
Again we can parse, and notice that the integer values are longer this time:
30 46 02 21 00 e4e87c417196c6e5cd63f93e94929ccda6d04fc0a7446922baf3070e854ec4f4 02 21 00a1ecd098008329de9bc93fb2ded6aaceecc921f7183d6b3cfc673b3ef8af219e
In fact, we have 0x21 bytes for the values of r and s, but the first byte is a zero, so we actually have 0x20 bytes, and which is 32 bytes. This will give us 256 bits, and which fits with the curve.
Example
The DER for an ECDSA signature shows the r and s value:
echo 3046022100e4e87c417196c6e5cd63f93e94929ccda6d04fc0a7446922baf3070e854ec4f4022100a1ecd098008329de9bc93fb2ded6aaceecc921f7183d6b3cfc673b3ef8af219e | xxd -r -p | openssl asn1parse -inform der 0:d=0 hl=2 l= 70 cons: SEQUENCE 2:d=1 hl=2 l= 33 prim: INTEGER :E4E87C417196C6E5CD63F93E94929CCDA6D04FC0A7446922BAF3070E854EC4F4 37:d=1 hl=2 l= 33 prim: INTEGER :A1ECD098008329DE9BC93FB2DED6AACEECC921F7183D6B3CFC673B3EF8AF219E
We can see that there are two integer values (r an s), and which are 32 bytes (64 hex characters) long. This means that the values are 256 bits long, and which makes the signature around 512 bits (64 bytes long).
PEM and DER
One of the great things about cryptography is the way we have managed to migrate our methods. This migration is often required when we introduce new methods (such as with ECC) or where methods are deprecated (such as for DES). We thus have ways to define new methods and which can be easily interpreted by applications. At the core of this is the DER format for defining our cryptography.
We need ways to distribute our public keys, private keys and digital certificates in a portable format. One of the most common forms is Distinguished Encoding Rules (DER) encoding of ASN.1 (Abstract Syntax Notation One). Overall it is a truly binary representation of the encoded data.
The other common format is PEM, which converts the binary encoding into a text-readable format. With PEM we can encode cryptographic information in a Base64 ASCII format and with plain-text headers and footers of “ — — -BEGIN RSA PRIVATE KEY — — -” and “ — — -END RSA PRIVATE KEY — — -”, whereas with DER, we have binary format.
As example private key is [here]:
-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgbWQGAmoVJCTfv6dE N/55Al6OHA3DQ7GHesXij/WPQ0ShRANCAARidNTJiKD+rwqAITZNihCI6rWucirb XMr2lKGvXPHgeK9uBp5UQ14kW7SrMSh3hthlSOJuuSENzK9K0OmVm07K -----END PRIVATE KEY-----
A public key [here]:
-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYnTUyYig/q8KgCE2TYoQiOq1rnIq 21zK9pShr1zx4HivbgaeVENeJFu0qzEod4bYZUjibrkhDcyvStDplZtOyg== -----END PUBLIC KEY-----
A certificate [here]:
-----BEGIN CERTIFICATE----- MIIBaTCCAQ+gAwIBAgIBBDAKBggqhkjOPQQDAjAYMRYwFAYDVQQDDA1BU2VjdXJp dHlzaXRlMB4XDTE1MTIzMTIzNTk1OVoXDTI1MTIzMTIzNTk1OVowFTETMBEGA1UE AwwKV2ViIHNlcnZlcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGJ01MmIoP6v CoAhNk2KEIjqta5yKttcyvaUoa9c8eB4r24GnlRDXiRbtKsxKHeG2GVI4m65IQ3M r0rQ6ZWbTsqjTTBLMAkGA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMC4GA1UdHwQn MCUwI6AhoB+GHWh0dHA6Ly9ib2IuYXNlY3VyaXR5c2l0ZS5jb20vMAoGCCqGSM49 BAMCA0gAMEUCIQDJykBnped+C6r7SbhYFDMemMoUZ/6JaVmhGdOEnr8XxQIgVbhD xb8HiBvve7BxHnyHv7DREt7nE8fI3Y4IPu80ADw= -----END CERTIFICATE-----
DER format
This article will look at the DER format and use OpenSSL to decode a hex string and into its contents. Overall ASN.1 is used to define abstract types and values. One of the most basic types is SEQUENCE and is an ordered collection of one or more types. In DER, SEQUENCE is identified with a tag of “30”, and followed by a byte value for the length of the object defined. The other common types are OBJECT IDENTIFIER (and which has a tag of “06”) and BIT STRING (and which has a tag of “03”).
The object identifier tag is used to define the cryptography methods used. An example identifier for ECC encryption is “1.2.840.10045.2.1”, and where 1 is OSI, 2 is member body, 840 is US (ANSI), and 10045 is “ansi-X9–62”, and “2” is key type [1]. Other common algorithms are: “1.2.840.113549.1.1.1” (X509 RSA), “1.2.840.10040.4.1” (X509 Digital Signature Standard -DSS), and “1.2.840.10046.2.1” (Diffie-Hellman — DH). The following is an example of the hex sequence for an object ID, and where we have the “06” tag, followed by an identifier for seven bytes (“07”), and then the Object ID of seven bytes (“2a8648ce3d0201”):
06 07 2a8648ce3d0201 # Object ID - 7 bytes long: 1.2.840.10045.2.1 (ECC)
We can also define the curve type in the object identifier, and where we have the form of iso(1), member-body(2), us(840), ansi-X9–62(10045), curves(3), prime(1). For example, 1.2.840.10045.3.1.7 defines ECDSA P-256. Other examples are SECP192R1 (“1.2.840.10045.3.1.1”), SECP224R1 (“1.3.132.0.33”), SECP256K1 (“1.3.132.0.10”), SECP256R1 (“1.2.840.10045.3.1.7”), SECP384R1 (“1.3.132.0.34”), SECP521R1 (“1.3.132.0.35”), and BRAINPOOLP256R1 (“1.3.36.3.3.2.8.1.1.7”). An example where we have an identifier (“06”), followed by the number of bytes identifier (“08”) and Object ID of eight bytes (“2a8648ce3d030107”):
06 08 2a8648ce3d030107 # Object ID - 8 bytes long: 1.2.840.10045.3.1.7 (ECDSA P256)
For the “03” tag, we define a bitstream for k
eys. In the following, we have “03”, followed by the number of bytes (66 bytes) for the keys, and then the keys are defined after this (64 bytes):03 42 # Bit stream - 0x42 (66 bytes long) 0004 # Identifies public key 2927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838 # Identifies public key x co-ordinate c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e # Identifies public key y co-ordinate
An example hex string for a DER format for ECC public keys is:
3059301306072a8648ce3d020106082a8648ce3d030107034200042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513
We can then break down with:
30 59 # Sequence length 0x59 - 91 bytes long 30 13 # Sequence length 0x13 - 21 bytes long 06 07 2a8648ce3d0201 # Object ID - 7 bytes long - 1.2.840.10045.2.1 (ECC) 06 08 2a8648ce3d030107 # Object ID - 8 bytes long - 1.2.840.10045.3.1.7 (ECDSA P256) 03 42 # Bit stream - 0x42 (66 bytes long) 0004 # Identifies public key 2927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838 # Identifies public key x co-ordinate c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e # Identifies public key y co-ordinate
If we OpenSSL, we can use xdd to conver the hex string into a byte array, and then parse for ASN1.
echo 3059301306072a8648ce3d020106082a8648ce3d030107034200042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e | xxd -r -p | openssl asn1parse -inform der 0:d=0 hl=2 l= 89 cons: SEQUENCE 2:d=1 hl=2 l= 19 cons: SEQUENCE 4:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey 13:d=2 hl=2 l= 8 prim: OBJECT :prime256v1 23:d=1 hl=2 l= 66 prim: BIT STRING 0000 - 00 04 29 27 b1 05 12 ba-e3 ed dc fe 46 78 28 12 ..)'........Fx(. 0010 - 8b ad 29 03 26 99 19 f7-08 60 69 c8 c4 df 6c 73 ..)......`i...ls 0020 - 28 38 c7 78 79 64 ea ac-00 e5 92 1f b1 49 8a 60 (8.xyd.......I.` 0030 - f4 60 67 66 b3 d9 68 50-01 55 8d 1a 97 4e 73 41 .`gf..hP.U...NsA 0040 - 51 3e Q.
We can see that the bit string has a “00 04” are the start, and the followed by the x-ordinate (0x2917 …) and (y-coordinate (0xc778…) of the public key.
≈ASN1 parsing for RSA keys
For RSA keys, we can generate a 512 bit key pair, and then convert to a hex DER string with:
% openssl genrsa 512 | openssl rsa -outform der | xxd -plain writing RSA key 30820154020100300d06092a864886f70d01010105000482013e3082013a 020100024100beeaa2dd3aa43a38904f57707465532c31ffe5904e2335d5 4a26b7ba046846a165cd72090e0b109cae3fc5e95e0c89d7531ffeaad319 3b0e9dae357481fac16102030100010240632216fb64edcdc57d39959fb0 f0c42558b9158cbee60d97f8eeedcbcb6a11f1e99d45890de936eb167c53 f45876d2fed8f31a889ab6029cc04b6d09e2657681022100f6cb29b3286f 46e3f025d9e71afb84aeb1e19fe874378d490e73ef2a3fab8ae9022100c6 09db5bb1906090e80d79d69109ec37359bafc06b9ed9fff97a5bcb9c1f07 b902201fdf7d333635a8e22751bc22acc96f0960cfd2e7229b4a13f559e3 b9811f96810221008e92de17df77ffed9302d0aa86f4cbd8db81604079ba 7d5be9f20b5044ef9a1102203092b0733f508f646ec80520db410860ebe7 d4bb15db8ad3703666b57b41fe75
For a 1,024-bit modulus, we generate a key pair with:
3082013a020100024100a53c172810b45f94cb1edbd6a7eeffd5fa94b4c692d00bec0760bc53dff8b3034ed82f92debb553ae6fc0663ab90247e71af25a7643ad055cec78345b92c36c7020301000102403de24885efe3ae1c8b0a6ea97151d8ad6a610167919aabac6582fc65a96f7a937b40f06c5594f0fe1a74b957e731a021f9b214bfcb7fecfee328a9b041eb8d41022100da6a12118066136f8b1478dd78625bb80ed2ffa6b525a0cbc89058a37040faa7022100c1ab44c7fd1fcc554f4143072361f0bf064619b25fcc54064def8692da5006e102203bf114585d46a65adc6e97e5201ece512b30591d2565d845551bd857b27d02cd02201acc03dc581dca3c1397481af8be587d55f4e521553804784ad8559a84328681022100bd24bc374ab656a11b2d531ac4eaebb5dd013848c654268d453942a39248bc32
If we parse we get [here]:
echo 3082013a020100024100a53c172810b45f94cb1edbd6a7eeffd5fa94b4c692d00bec0760bc53dff8b3034ed82f92debb553ae6fc0663ab90247e71af25a7643ad055cec78345b92c36c7020301000102403de24885efe3ae1c8b0a6ea97151d8ad6a610167919aabac6582fc65a96f7a937b40f06c5594f0fe1a74b957e731a021f9b214bfcb7fecfee328a9b041eb8d41022100da6a12118066136f8b1478dd78625bb80ed2ffa6b525a0cbc89058a37040faa7022100c1ab44c7fd1fcc554f4143072361f0bf064619b25fcc54064def8692da5006e102203bf114585d46a65adc6e97e5201ece512b30591d2565d845551bd857b27d02cd02201acc03dc581dca3c1397481af8be587d55f4e521553804784ad8559a84328681022100bd24bc374ab656a11b2d531ac4eaebb5dd013848c654268d453942a39248bc32 | xxd -r -p | openssl asn1parse -inform der 0:d=0 hl=4 l= 314 cons: SEQUENCE 4:d=1 hl=2 l= 1 prim: INTEGER :00 7:d=1 hl=2 l= 65 prim: INTEGER :A53C172810B45F94CB1EDBD6A7EEFFD5FA94B4C692D00BEC0760BC53DFF8B3034ED82F92DEBB553AE6FC0663AB90247E71AF25A7643AD055CEC78345B92C36C7 74:d=1 hl=2 l= 3 prim: INTEGER :010001 79:d=1 hl=2 l= 64 prim: INTEGER :3DE24885EFE3AE1C8B0A6EA97151D8AD6A610167919AABAC6582FC65A96F7A937B40F06C5594F0FE1A74B957E731A021F9B214BFCB7FECFEE328A9B041EB8D41 145:d=1 hl=2 l= 33 prim: INTEGER :DA6A12118066136F8B1478DD78625BB80ED2FFA6B525A0CBC89058A37040FAA7 180:d=1 hl=2 l= 33 prim: INTEGER :C1AB44C7FD1FCC554F4143072361F0BF064619B25FCC54064DEF8692DA5006E1 215:d=1 hl=2 l= 32 prim: INTEGER :3BF114585D46A65ADC6E97E5201ECE512B30591D2565D845551BD857B27D02CD 249:d=1 hl=2 l= 32 prim: INTEGER :1ACC03DC581DCA3C1397481AF8BE587D55F4E521553804784AD8559A84328681 283:d=1 hl=2 l= 33 prim: INTEGER :BD24BC374AB656A11B2D531AC4EAEBB5DD013848C654268D453942A39248BC32
The values in sequence are N, e, d, p, q, and where:
N=A53C172810B45F94CB1EDBD6A7EEFFD5FA94B4C692D00BEC0760BC53DFF8B3034ED82F92DEBB553AE6FC0663AB90247E71AF25A7643AD055CEC78345B92C36C7 e=010001 d=3DE24885EFE3AE1C8B0A6EA97151D8AD6A610167919AABAC6582FC65A96F7A937B40F06C5594F0FE1A74B957E731A021F9B214BFCB7FECFEE328A9B041EB8D41 p=DA6A12118066136F8B1478DD78625BB80ED2FFA6B525A0CBC89058A37040FAA7 q=C1AB44C7FD1FCC554F4143072361F0BF064619B25FCC54064DEF8692DA5006E1
We see that N has 128 bytes, and so is 1,024 bits long, and that p and q are 512 bit long. If we multiply p times q we get the modulus value:
>>> p=int("DA6A12118066136F8B1478DD78625BB80ED2FFA6B525A0CBC89058A37040FAA7",16) >>> q=int("C1AB44C7FD1FCC554F4143072361F0BF064619B25FCC54064DEF8692DA5006E1",16) >>> hex(p*q) '0xa53c172810b45f94cb1edbd6a7eeffd5fa94b4c692d00bec0760bc53dff8b3034ed82f92debb553ae6fc0663ab90247e71af25a7643ad055cec78345b92c36c7'
and which is equal to the modulus (N).
ASN.1 parsing for ECC keys
We can generate a secp256k1 private key with (the -noout option does not generate the ECC parameters):
% openssl ecparam -genkey -name secp256r1 -outform der -noout | xxd -plain using curve name prime256v1 instead of secp256r1 3077020101042037c67057c9350ce42d9aa365aa874c2f62db43bf309667 a25e5cae0a958c1065a00a06082a8648ce3d030107a144034200048bd1ff 01a5fe5cda52970c65d0cf41265391b4520d7709832a71f1af4796b8234f 212b7d8840ae45ee1f98c8b65e2d84fe64c6e5d709a206a1388f66eff58b 51
If we parse we get [here]:
DER string: 3077020101042037c67057c9350ce42d9aa365aa874c2f62db43bf309667a25e5cae0a958c1065a00a06082a8648ce3d030107a144034200048bd1ff01a5fe5cda52970c65d0cf41265391b4520d7709832a71f1af4796b8234f212b7d8840ae45ee1f98c8b65e2d84fe64c6e5d709a206a1388f66eff58b51 ==Sequence== Integer (02): 0x1 --->Sequence (A0) --->Obj ID tag (06 - Object ID) ID algorithm: 1.2.840.10045.3.1.7 secp256r1 --->Sequence (A1) Bit value: b'048bd1ff01a5fe5cda52970c65d0cf41265391b4520d7709832a71f1af4796b8234f212b7d8840ae45ee1f98c8b65e2d84fe64c6e5d709a206a1388f66eff58b51'
If we use OpenSSL to parse we get [here]:
echo 3077020101042037c67057c9350ce42d9aa365aa874c2f62db43bf309667a25e5cae0a958c1065a00a06082a8648ce3d030107a144034200048bd1ff01a5fe5cda52970c65d0cf41265391b4520d7709832a71f1af4796b8234f212b7d8840ae45ee1f98c8b65e2d84fe64c6e5d709a206a1388f66eff58b51 | xxd -r -p | openssl asn1parse -inform der 0:d=0 hl=2 l= 119 cons: SEQUENCE 2:d=1 hl=2 l= 1 prim: INTEGER :01 5:d=1 hl=2 l= 32 prim: OCTET STRING 0000 - 37 c6 70 57 c9 35 0c e4-2d 9a a3 65 aa 87 4c 2f 7.pW.5..-..e..L/ 0010 - 62 db 43 bf 30 96 67 a2-5e 5c ae 0a 95 8c 10 65 b.C.0.g.^\.....e 39:d=1 hl=2 l= 10 cons: cont [ 0 ] 41:d=2 hl=2 l= 8 prim: OBJECT :prime256v1 51:d=1 hl=2 l= 68 cons: cont [ 1 ] 53:d=2 hl=2 l= 66 prim: BIT STRING 0000 - 00 04 8b d1 ff 01 a5 fe-5c da 52 97 0c 65 d0 cf ........\.R..e.. 0010 - 41 26 53 91 b4 52 0d 77-09 83 2a 71 f1 af 47 96 A.S..R.w..*q..G. 0020 - b8 23 4f 21 2b 7d 88 40-ae 45 ee 1f 98 c8 b6 5e .#O!+}...E.....^ 0030 - 2d 84 fe 64 c6 e5 d7 09-a2 06 a1 38 8f 66 ef f5 -..d.......8.f.. 0040 - 8b 51
The 00 04 part of the key identifies that it is an uncompressed point, and thus has an (x,y) value. Thus we have 64 bytes for the key, of which 32 bytes (256 bits) for an x co-ordinate value, and 32 byte for a y co-ordinate value.