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.
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
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}
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}
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 = "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 =
{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'{ ...}
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
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),
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),
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 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}, []}]
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"}]}]
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,"foo@bar.com"},
{hostnames,["|1|BWO5qDxk/cFH0wa05JLdHn+j6xQ=|rXQvIxh5cDD3C43k5DPDamawVNA="]}]}]
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"}]}]
If you got a public key
N> SshBin = public_key:ssh_encode([{PubKey, Attributes}], openssh_public_key),
<<"ssh-rsa "...>>
N+1> file:write_file("id_rsa.pub", SshBin).
ok