4 Using the public_key API
4.1 General information
This chapter is dedicated to showing some examples of how to use the public_key API. Keys and certificates used in the following sections are generated only for the purpose of testing the public key application.
Note that some shell printouts, in the following examples, have been abbreviated for increased readability.
4.2 PEM files
Pulic key data (keys, certificates etc) may be stored in PEM format. PEM files comes from the Private Enhanced Mail Internet standard and has a structure that looks like this:
<text> -----BEGIN <SOMETHING>----- <Attribute> : <Value> <Base64 encoded DER data> -----END <SOMETHING>----- <text>
A file can contain several BEGIN/END blocks. Text lines between blocks are ignored. Attributes, if present, are currently ignored except for Proc-Type and DEK-Info that are used when the DER data is encrypted.
DSA private key
Note file handling is not done by the public_key application.
1> {ok, PemBin} = file:read_file("dsa.pem"). {ok,<<"-----BEGIN DSA PRIVATE KEY-----\nMIIBuw"...>>}
This PEM file only has one entry a private DSA key.
2> [DSAEntry] = public_key:pem_decode(PemBin). [{'DSAPrivateKey',<<48,130,1,187,2,1,0,2,129,129,0,183, 179,230,217,37,99,144,157,21,228,204, 162,207,61,246,...>>, not_encrypted}]
3> Key = public_key:pem_entry_decode(DSAEntry). #'DSAPrivateKey'{version = 0, p = 12900045185019966618...6593, q = 1216700114794736143432235288305776850295620488937, g = 10442040227452349332...47213, y = 87256807980030509074...403143, x = 510968529856012146351317363807366575075645839654}
RSA private key encrypted with a password.
1> {ok, PemBin} = file:read_file("rsa.pem"). {ok,<<"Bag Attribut"...>>}
This PEM file only has one entry a private RSA key.
2>[RSAEntry] = public_key:pem_decode(PemBin). [{'RSAPrivateKey',<<224,108,117,203,152,40,15,77,128,126, 221,195,154,249,85,208,202,251,109, 119,120,57,29,89,19,9,...>>, {"DES-EDE3-CBC",<<"kÙeø¼pµL">>}}]
In this example the password is "abcd1234".
3> Key = public_key:pem_entry_decode(RSAEntry, "abcd1234"). #'RSAPrivateKey'{version = 'two-prime', modulus = 1112355156729921663373...2737107, publicExponent = 65537, privateExponent = 58064406231183...2239766033, prime1 = 11034766614656598484098...7326883017, prime2 = 10080459293561036618240...77738643771, exponent1 = 77928819327425934607...22152984217, exponent2 = 36287623121853605733...20588523793, coefficient = 924840412626098444...41820968343, otherPrimeInfos = asn1_NOVALUE}
X509 Certificates
1> {ok, PemBin} = file:read_file("cacerts.pem"). {ok,<<"-----BEGIN CERTIFICATE-----\nMIIC7jCCAl"...>>}
This file includes two certificates
2> [CertEntry1, CertEntry2] = public_key:pem_decode(PemBin). [{'Certificate',<<48,130,2,238,48,130,2,87,160,3,2,1,2,2, 9,0,230,145,97,214,191,2,120,150,48,13, ...>>, not_encrypted}, {'Certificate',<<48,130,3,200,48,130,3,49,160,3,2,1,2,2,1, 1,48,13,6,9,42,134,72,134,247,...>>>, not_encrypted}]
Certificates may of course be decoded as usual ...
2> Cert = public_key:pem_entry_decode(CertEntry1). #'Certificate'{ tbsCertificate = #'TBSCertificate'{ version = v3,serialNumber = 16614168075301976214, signature = #'AlgorithmIdentifier'{ algorithm = {1,2,840,113549,1,1,5}, parameters = <<5,0>>}, issuer = {rdnSequence, [[#'AttributeTypeAndValue'{ type = {2,5,4,3}, value = <<19,8,101,114,108,97,110,103,67,65>>}], [#'AttributeTypeAndValue'{ type = {2,5,4,11}, value = <<19,10,69,114,108,97,110,103,32,79,84,80>>}], [#'AttributeTypeAndValue'{ type = {2,5,4,10}, value = <<19,11,69,114,105,99,115,115,111,110,32,65,66>>}], [#'AttributeTypeAndValue'{ type = {2,5,4,7}, value = <<19,9,83,116,111,99,107,104,111,108,109>>}], [#'AttributeTypeAndValue'{ type = {2,5,4,6}, value = <<19,2,83,69>>}], [#'AttributeTypeAndValue'{ type = {1,2,840,113549,1,9,1}, value = <<22,22,112,101,116,101,114,64,101,114,...>>}]]}, validity = #'Validity'{ notBefore = {utcTime,"080109082929Z"}, notAfter = {utcTime,"080208082929Z"}}, subject = {rdnSequence, [[#'AttributeTypeAndValue'{ type = {2,5,4,3}, value = <<19,8,101,114,108,97,110,103,67,65>>}], [#'AttributeTypeAndValue'{ type = {2,5,4,11}, value = <<19,10,69,114,108,97,110,103,32,79,84,80>>}], [#'AttributeTypeAndValue'{ type = {2,5,4,10}, value = <<19,11,69,114,105,99,115,115,111,110,32,...>>}], [#'AttributeTypeAndValue'{ type = {2,5,4,7}, value = <<19,9,83,116,111,99,107,104,111,108,...>>}], [#'AttributeTypeAndValue'{ type = {2,5,4,6}, value = <<19,2,83,69>>}], [#'AttributeTypeAndValue'{ type = {1,2,840,113549,1,9,1}, value = <<22,22,112,101,116,101,114,64,...>>}]]}, subjectPublicKeyInfo = #'SubjectPublicKeyInfo'{ algorithm = #'AlgorithmIdentifier'{ algorithm = {1,2,840,113549,1,1,1}, parameters = <<5,0>>}, subjectPublicKey = {0,<<48,129,137,2,129,129,0,203,209,187,77,73,231,90,...>>}}, issuerUniqueID = asn1_NOVALUE, subjectUniqueID = asn1_NOVALUE, extensions = [#'Extension'{ extnID = {2,5,29,19}, critical = true, extnValue = [48,3,1,1,255]}, #'Extension'{ extnID = {2,5,29,15}, critical = false, extnValue = [3,2,1,6]}, #'Extension'{ extnID = {2,5,29,14}, critical = false, extnValue = [4,20,27,217,65,152,6,30,142|...]}, #'Extension'{ extnID = {2,5,29,17}, critical = false, extnValue = [48,24,129,22,112,101,116,101|...]}]}, signatureAlgorithm = #'AlgorithmIdentifier'{ algorithm = {1,2,840,113549,1,1,5}, parameters = <<5,0>>}, signature = {0, <<163,186,7,163,216,152,63,47,154,234,139,73,154,96,120, 165,2,52,196,195,109,167,192,...>>}}
Parts of certificates can be decoded with public_key:der_decode/2 using that parts ASN.1 type. Although application specific certificate extension requires application specific ASN.1 decode/encode-functions. Example, the first value of the rdnSequence above is of ASN.1 type 'X520CommonName'. ({2,5,4,3} = ?id-at-commonName)
public_key:der_decode('X520CommonName', <<19,8,101,114,108,97,110,103,67,65>>). {printableString,"erlangCA"}
... but certificates can also be decode using the pkix_decode_cert/2 that can customize and recursively decode standard parts of a certificate.
3>{_, DerCert, _} = CertEntry1.
4> public_key:pkix_decode_cert(DerCert, otp). #'OTPCertificate'{ tbsCertificate = #'OTPTBSCertificate'{ version = v3,serialNumber = 16614168075301976214, signature = #'SignatureAlgorithm'{ algorithm = {1,2,840,113549,1,1,5}, parameters = 'NULL'}, issuer = {rdnSequence, [[#'AttributeTypeAndValue'{ type = {2,5,4,3}, value = {printableString,"erlangCA"}}], [#'AttributeTypeAndValue'{ type = {2,5,4,11}, value = {printableString,"Erlang OTP"}}], [#'AttributeTypeAndValue'{ type = {2,5,4,10}, value = {printableString,"Ericsson AB"}}], [#'AttributeTypeAndValue'{ type = {2,5,4,7}, value = {printableString,"Stockholm"}}], [#'AttributeTypeAndValue'{type = {2,5,4,6},value = "SE"}], [#'AttributeTypeAndValue'{ type = {1,2,840,113549,1,9,1}, value = "[email protected]"}]]}, validity = #'Validity'{ notBefore = {utcTime,"080109082929Z"}, notAfter = {utcTime,"080208082929Z"}}, subject = {rdnSequence, [[#'AttributeTypeAndValue'{ type = {2,5,4,3}, value = {printableString,"erlangCA"}}], [#'AttributeTypeAndValue'{ type = {2,5,4,11}, value = {printableString,"Erlang OTP"}}], [#'AttributeTypeAndValue'{ type = {2,5,4,10}, value = {printableString,"Ericsson AB"}}], [#'AttributeTypeAndValue'{ type = {2,5,4,7}, value = {printableString,"Stockholm"}}], [#'AttributeTypeAndValue'{type = {2,5,4,6},value = "SE"}], [#'AttributeTypeAndValue'{ type = {1,2,840,113549,1,9,1}, value = "[email protected]"}]]}, subjectPublicKeyInfo = #'OTPSubjectPublicKeyInfo'{ algorithm = #'PublicKeyAlgorithm'{ algorithm = {1,2,840,113549,1,1,1}, parameters = 'NULL'}, subjectPublicKey = #'RSAPublicKey'{ modulus = 1431267547247997...37419, publicExponent = 65537}}, issuerUniqueID = asn1_NOVALUE, subjectUniqueID = asn1_NOVALUE, extensions = [#'Extension'{ extnID = {2,5,29,19}, critical = true, extnValue = #'BasicConstraints'{ cA = true,pathLenConstraint = asn1_NOVALUE}}, #'Extension'{ extnID = {2,5,29,15}, critical = false, extnValue = [keyCertSign,cRLSign]}, #'Extension'{ extnID = {2,5,29,14}, critical = false, extnValue = [27,217,65,152,6,30,142,132,245|...]}, #'Extension'{ extnID = {2,5,29,17}, critical = false, extnValue = [{rfc822Name,"[email protected]"}]}]}, signatureAlgorithm = #'SignatureAlgorithm'{ algorithm = {1,2,840,113549,1,1,5}, parameters = 'NULL'}, signature = {0, <<163,186,7,163,216,152,63,47,154,234,139,73,154,96,120, 165,2,52,196,195,109,167,192,...>>}}
This call is equivalent to public_key:pem_entry_decode(CertEntry1)
5> public_key:pkix_decode_cert(DerCert, plain). #'Certificate'{ ...}
Encoding public key data to PEM format
If you have public key data and and want to create a PEM file you can do that by calling the functions public_key:pem_entry_encode/2 and pem_encode/1 and then saving the result to a file. For example assume you have PubKey = 'RSAPublicKey'{} then you can create a PEM-"RSA PUBLIC KEY" file (ASN.1 type 'RSAPublicKey') or a PEM-"PUBLIC KEY" file ('SubjectPublicKeyInfo' ASN.1 type).
The second element of the PEM-entry will be the ASN.1 DER encoded key data.
1> PemEntry = public_key:pem_entry_encode('RSAPublicKey', RSAPubKey). {'RSAPublicKey', <<48,72,...>>, not_encrypted} 2> PemBin = public_key:pem_encode([PemEntry]). <<"-----BEGIN RSA PUBLIC KEY-----\nMEgC...>> 3> file:write_file("rsa_pub_key.pem", PemBin). ok
or
1> PemBin = public_key:pem_entry_encode('SubjectPublicKeyInfo', RSAPubKey). {'SubjectPublicKeyInfo', <<48,92...>>, not_encrypted} 2> PemBin = public_key:pem_encode([PemEntry]). <<"-----BEGIN PUBLIC KEY-----\nMFw...>> 3> file:write_file("pub_key.pem", PemBin). ok
4.3 RSA public key cryptography
Suppose you have PrivateKey = #'RSAPrivateKey{}' and the plaintext Msg = binary() and the corresponding public key PublicKey = #'RSAPublicKey'{} then you can do the following. Note that you normally will only do one of the encrypt or decrypt operations and the peer will do the other.
Encrypt with the private key
RsaEncrypted = public_key:encrypt_private(Msg, PrivateKey), Msg = public_key:decrypt_public(RsaEncrypted, PublicKey),
Encrypt with the public key
RsaEncrypted = public_key:encrypt_public(Msg, PublicKey), Msg = public_key:decrypt_private(RsaEncrypted, PrivateKey),
4.4 Digital signatures
Suppose you have PrivateKey = #'RSAPrivateKey{}'or #'DSAPrivateKey'{} and the plaintext Msg = binary() and the corresponding public key PublicKey = #'RSAPublicKey'{} or {integer(), #'DssParams'{}} then you can do the following. Note that you normally will only do one of the sign or verify operations and the peer will do the other.
Signature = public_key:sign(Msg, sha, PrivateKey), true = public_key:verify(Msg, sha, Signature, PublicKey),
It might be appropriate to calculate the message digest before calling sign or verify and then you can use the none as second argument.
Digest = crypto:sha(Msg), Signature = public_key:sign(Digest, none, PrivateKey), true = public_key:verify(Digest, none, Signature, PublicKey),
4.5 SSH files
SSH typically uses PEM files for private keys but has its own file format for storing public keys. The erlang public_key application can be used to parse the content of SSH public key files.
RFC 4716 SSH public key files
RFC 4716 SSH files looks confusingly like PEM files, but there are some differences.
1> {ok, SshBin} = file:read_file("ssh2_rsa_pub"). {ok, <<"---- BEGIN SSH2 PUBLIC KEY ----\nAAAA"...>>}
This is equivalent to calling public_key:ssh_decode(SshBin, rfc4716_public_key).
2> public_key:ssh_decode(SshBin, public_key). [{#'RSAPublicKey'{modulus = 794430685...91663, publicExponent = 35}, []}]
Openssh public key format
1> {ok, SshBin} = file:read_file("openssh_dsa_pub"). {ok,<<"ssh-dss AAAAB3Nza"...>>}
This is equivalent to calling public_key:ssh_decode(SshBin, openssh_public_key).
2> public_key:ssh_decode(SshBin, public_key). [{{15642692...694280725, #'Dss-Parms'{p = 17291273936...696123221, q = 1255626590179665817295475654204371833735706001853, g = 10454211196...480338645}}, [{comment,"dhopson@VMUbuntu-DSH"}]}]
Known hosts - openssh format
1> {ok, SshBin} = file:read_file("known_hosts"). {ok,<<"hostname.domain.com,192.168.0.1 ssh-rsa AAAAB...>>}
Returns a list of public keys and their related attributes each pair of key and attributes corresponds to one entry in the known hosts file.
2> public_key:ssh_decode(SshBin, known_hosts). [{#'RSAPublicKey'{modulus = 1498979460408...72721699, publicExponent = 35}, [{hostnames,["hostname.domain.com","192.168.0.1"]}]}, {#'RSAPublicKey'{modulus = 14989794604088...2721699, publicExponent = 35}, [{comment,"[email protected]"}, {hostnames,["|1|BWO5qDxk/cFH0wa05JLdHn+j6xQ=|rXQvIxh5cDD3C43k5DPDamawVNA="]}]}]
Authorized keys - openssh format
1> {ok, SshBin} = file:read_file("auth_keys"). {ok, <<"command=\"dump /home\",no-pty,no-port-forwarding ssh-rsa AAA...>>}
Returns a list of public keys and their related attributes each pair of key and attributes corresponds to one entry in the authorized key file.
2> public_key:ssh_decode(SshBin, auth_keys). [{#'RSAPublicKey'{modulus = 794430685...691663, publicExponent = 35}, [{comment,"dhopson@VMUbuntu-DSH"}, {options,["command=\"dump/home\"","no-pty", "no-port-forwarding"]}]}, {{1564269258491...607694280725, #'Dss-Parms'{p = 17291273936185...763696123221, q = 1255626590179665817295475654204371833735706001853, g = 10454211195705...60511039590076780999046480338645}}, [{comment,"dhopson@VMUbuntu-DSH"}]}]
Creating an SSH file from public key data
If you got a public key PubKey and a related list of attributes Attributes as returned by ssh_decode/2 you can create a new ssh file for example
N> SshBin = public_key:ssh_encode([{PubKey, Attributes}], openssh_public_key), <<"ssh-rsa "...>> N+1> file:write_file("id_rsa.pub", SshBin). ok