A public/private keypair consists of a modulus, a private exponent, and a public exponent. The public key contains the modulus and the public exponent. The private key contains the modulus and the private exponent (http://en.wikipedia.org/wiki/RSA_(algorithm)).
PEM and DER are not really key formats, but rather a ways of serializing certain data structures. Knowing that you have a PEM or DER file is kind of like knowing that you have a CSV or a JSON document. It tells you about the syntax, but it doesn't tell you what the content means. The best way to find out what format your key is stored in is to dump view the encoded structure a DER-inspection tool.
A PEM file is a bas64-encoded DER file with a text header and footer.
More specifically, pem-data = "-----BEGIN PUBLIC KEY-----" + newline + base64( der-data ) + newline + "-----END PUBLIC KEY-----". For example:-----BEGIN PUBLIC KEY----- MIIBCgKCAQEA8bx++TdhOI+4akK+MD6DfVf/jQWFyq3vN8YuRMB/LToZuLcnTclH 4QYua+rHi3XMuF1Hl0vVVvpU0Yjowj1E7GBJOV4qvcLOcyXfnXOO/+WMzOHeZTw9 A/LGfjiU+y6IcawwdbDPnDdKWc6B8KX/QALwa0JxWzzCNOgCdgmN4oE7tWGfS9O7 PMObAxsGdpgQ3y+5ugOnmQuXYKGl4Ii4xaW2Izg1SdYM23WA+f89JsSP9cEvlnpz 0yY6wkUv6tnp+nNFwGoNA8BYVtbKdXxRX2q49PZg7Dnl3F2i10DoAilaczJfgkAt oZTHG3YoXv/QRpeuHf/5RhupCJTm/DyQHQIDAQAB -----END PUBLIC KEY-----
"DER" stands for 'Distinguished Encoding Rules', an ASN.1-based format for serializing structured information. Saying that a key is DER-encoded does not give a full picture, because you still need to know the encoded structure. I have seen both Public Key Info and RSA Public Key structures emitted by OpenSSL libraries. Both types of structures are described by X.509.
Since a public key is made of 2 integers, there is no such thing as 'raw public key data'. Some method of serialization must be used to encode the numbers as a byte stream. Usually public keys are stored as DER-serializations of one of the following X.509 structures:
A signature is an ASN.1 structure that includes a Public Key Info structure along with a bunch of other stuff. I do not wish to familiarize myself with the details of 'other stuff', but read on for details about the public key structures.
A Public Key Info is an ASN.1 sequence containing 2 elements: a
header giving information about the algorithm used (this header is
itself a sequence, with the name of the algorithm as the first element
("rsaEncryption
", for RSA keys, and I don't know what the
other element(s) are for; the ones I've looked at are empty), and a
DER-encoded RSA Public Key structure (read: not the RSA Public Key
sequence itself, but DER-serialized, so it will appear in the Public
Key Info sequence as a BitString) providing the actual key data.
i.e. public-key-info = [ ["rsaEncryption", nil], der(
rsa-public-key ) ]
, "rsaEncryption" is an object id (tag=6) and
der( rsa-public-key )
indicates
an RSA Public Key structure serialized
using DER rules.
An RSA Public Key is an ASN.1 sequence containing 2 integers: the modulus (this should be a very large number) and the public exponent (a smaller one, usually 3, 17, or 65537). When encoded as an RSA Public Key in DER, a 2048-bit key usually takes up exactly 270 bytes.
i.e. [ exponent, modulus ]
, where exponent and modulus
are integers.
Java's PublicKey#getEncoded()
function returns a
DER-encoded structure that includes a header
("rsaEncryption
") and a BitString (tag=5) that includes
the 'raw key data.' To inspect the structure of a DER-encoded key, you
can use Ruby's OpenSSL::ASN1
module:
asn1 = OpenSSL::ASN1.decode(File.read('public-key'))
Generated with the help of some functions I wrote.
function derToPem($der) { $pem = chunk_split(base64_encode($der), 64, "\n"); $pem = "-----BEGIN PUBLIC KEY-----\n".$pem."-----END PUBLIC KEY-----\n"; return $pem; } function pemToDer($pem) { if( !preg_match('#--+BEGIN PUBLIC KEY--+\n(.*)\n--+END PUBLIC KEY--+#s', $pem, $bif) ) { throw new Exception("Failed to parse PEM data: $pem"); } $base64 = $bif[1]; return base64_decode($base64); } $keyPair = openssl_pkey_new( array( 'digest_alg' => 'sha1', 'private_key_bits' => 2048, // For faster unit testing 'private_key_type' => OPENSSL_KEYTYPE_RSA ) ); $det = openssl_pkey_get_details($keyPair); /** PEM-formatted public key */ $pubKeyPem = $det['key']; $pubKeyDer = pemToDer($pubKeyPem);
At this point $pubKeyDer
is the following DER-encoded structure:
Sequence[ Sequence[ ObjectID:"rsaEncryption", null ], BitString (270 bytes) ]
package togos.cryptosandbox; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; public class RSAKeyGenerator { protected static KeyPair generateKeyPair() { try { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); // On Harold... // 1024 bits = pretty much immediate // 2048 bits = takes a couple seconds // 4096 bits = takes many seconds kpg.initialize(2048); return kpg.generateKeyPair(); } catch( NoSuchAlgorithmException e ) { throw new RuntimeException(e); } } public static void writeFile( String filename, byte[] data ) throws IOException { File f = new File(filename); if( !f.getParentFile().exists() ) f.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(f); fos.write(data); fos.close(); } public static void main(String[] args) throws NoSuchAlgorithmException, IOException { KeyPair keyPair = generateKeyPair(); writeFile( "generated-keys/java/private-key", keyPair.getPrivate().getEncoded() ); writeFile( "generated-keys/java/public-key", keyPair.getPublic().getEncoded() ); } }
The generated public key is the exact same structure as that generated by PHP:
Sequence[ Sequence[ ObjectID:"rsaEncryption", null ], BitString (270 bytes) ]
The private key is similar but has an integer and an OctetString instead of a BitString:
Sequence[ Integer:0, Sequence[ ObjectID:"rsaEncryption", null ], OctetString (1192 bytes) ]