3 Getting Started
This section describes examples of how to use the Public Key API. Keys and certificates used in the following sections are generated only for testing the Public Key application.
Some shell printouts in the following examples are abbreviated for increased readability.
3.1 PEM Files
Public-key data (keys, certificates, and so on) can be stored in Privacy Enhanced Mail (PEM) format. The PEM files have the following structure:
<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 ignored except for Proc-Type and DEK-Info, which are used when DER data is encrypted.
DSA Private Key
A DSA private key can look as follows:
File handling is not done by the Public Key application.
1> {ok, PemBin} = file:read_file("dsa.pem"). {ok,<<"-----BEGIN DSA PRIVATE KEY-----\nMIIBuw"...>>}
The following PEM file has only 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 with Password
An RSA private key encrypted with a password can look as follows:
1> {ok, PemBin} = file:read_file("rsa.pem"). {ok,<<"Bag Attribut"...>>}
The following PEM file has only 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 following 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
The following is an example of X509 certificates:
1> {ok, PemBin} = file:read_file("cacerts.pem"). {ok,<<"-----BEGIN CERTIFICATE-----\nMIIC7jCCAl"...>>}
The following 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 can 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 = <<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 the ASN.1 type of that part. However, an application-specific certificate extension requires application-specific ASN.1 decode/encode-functions. In the recent example, the first value of rdnSequence 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"}
However, certificates can also be decoded using pkix_decode_cert/2, which 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 = "peter@erix.ericsson.se"}]]}, 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 = "peter@erix.ericsson.se"}]]}, 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,"peter@erix.ericsson.se"}]}]}, signatureAlgorithm = #'SignatureAlgorithm'{ algorithm = {1,2,840,113549,1,1,5}, parameters = 'NULL'}, signature = <<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 want to create a PEM file this can be done by calling functions public_key:pem_entry_encode/2 and pem_encode/1 and saving the result to a file. For example, assume that 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 is 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> PemEntry = 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
3.2 RSA Public-Key Cryptography
Suppose you have the following private key and a corresponding public key:
- PrivateKey = #'RSAPrivateKey{}' and the plaintext Msg = binary()
- PublicKey = #'RSAPublicKey'{}
Then you can proceed as follows:
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),
You normally do only one of the encrypt or decrypt operations, and the peer does the other. This normaly used in legacy applications as a primitive digital signature.
3.3 Digital Signatures
Suppose you have the following private key and a corresponding public key:
- PrivateKey = #'RSAPrivateKey{}' or #'DSAPrivateKey'{} and the plaintext Msg = binary()
- PublicKey = #'RSAPublicKey'{} or {integer(), #'DssParams'{}}
Then you can proceed as follows:
Signature = public_key:sign(Msg, sha, PrivateKey), true = public_key:verify(Msg, sha, Signature, PublicKey),
You normally do only one of the sign or verify operations, and the peer does the other.
It can be appropriate to calculate the message digest before calling sign or verify, and then use none as second argument:
Digest = crypto:sha(Msg), Signature = public_key:sign(Digest, none, PrivateKey), true = public_key:verify(Digest, none, Signature, PublicKey),
3.4 SSH Files
SSH typically uses PEM files for private keys but has its own file format for storing public keys. The 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
OpenSSH public-key format looks as follows:
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
Known hosts - OpenSSH format looks as follows:
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 attribute 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,"foo@bar.com"}, {hostnames,["|1|BWO5qDxk/cFH0wa05JLdHn+j6xQ=|rXQvIxh5cDD3C43k5DPDamawVNA="]}]}]
Authorized Keys - OpenSSH Format
Authorized keys - OpenSSH format looks as follows:
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 attribute 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