Hackflash #1 : Clé privée PKCS8 et formats ASN.1, DER et PEM

La quantité de formats que le profane rencontre en cryptographie a de quoi lui faire tourner la tête. Le sujet peut être d'autant plus déroutant que certains de ces formats semblent équivalents, au sens où ils permettent de véhiculer un même contenu, mais sous des formes différentes.
Tel est notamment le cas des formats ASN.1, DER et PEM, qu'il est possible d'utiliser pour stocker une clé privée générée avec l'algorithme RSA sous trois formes différentes.
Le PEM d'une clé privée
Bien connaître ces formats peut se révéler utile, notamment lors de challenges de sécurité informatique où il arrive d'avoir à forger des certificats et/ou des clés de manière tout à fait artisanale. Dans ce contexte, c'est le format ASN.1 qui peut être mobilisé.
Les brèves explications qui suivent visent à montrer, à travers l'exemple d'une petite clé privée générée avec l'algorithme RSA, comment il est possible de passer de l'un à l'autre de ces formats.
ASN.1, DER et PEM permettent de décrire exactement le même contenu, mais sous des formes chaque fois différentes.
Abstract Syntax Notation One (ASN.1) est une sorte de XML pour décrire une structure de données qui peut ainsi être sérialisée / désérialisée indépendamment de la plate-forme.
Par exemple, dans la RFC 5208 de PKCS #8 (très courte, au demeurant), la spécification du contenu est donnée sous la forme d'un module ASN.1 :
PrivateKeyInfo ::= SEQUENCE {
	version                   Version,
	privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
	privateKey                PrivateKey,
	attributes           [0]  IMPLICIT Attributes OPTIONAL }

Version ::= INTEGER

PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier

PrivateKey ::= OCTET STRING
AlgorithmIdentifier étant une structure définie dans la RFC 3447 de PKCS #1 :
ALGORITHM-IDENTIFIER ::= CLASS {
	&id    OBJECT IDENTIFIER  UNIQUE,
	&Type  OPTIONAL
}
	WITH SYNTAX { OID &id [PARAMETERS &Type] }
AlgorithmIdentifier { ALGORITHM-IDENTIFIER:InfoObjectSet } ::=
SEQUENCE {
	algorithm  ALGORITHM-IDENTIFIER.&id({InfoObjectSet}),
	parameters
		ALGORITHM-IDENTIFIER.&Type({InfoObjectSet}{@.algorithm})
			OPTIONAL
}
Par exemple, générons une clé privée avec l'algorithme RSA à l'aide de openssl, par défaut au format PKCS #8 et dans un fichier par défaut au format PEM :
# openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:512 -out key.pem
Le fichier PEM contient :
-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA846Z5qCtcPvmIjGT
IESUnFu/39iQ1ff7r/fIgjF9F5o49HiYvjv5l7ag7uzC4IZDXameveXRFA6hM9Ds
nc5KAQIDAQABAkAvgnGi+1TZ5UlPAjyg3r/egEc9cxFNq8H84WjLfLvaDmn7+ibk
Hkh2acPXvpMhM11pQtFrl30vNQnkeeQSCAABAiEA/6XPPPMlTpoIsIsY33RAnCd0
Stc6VGv4CIMBNGej0kECIQDz5IayqA+wBfca+RX+V1DlHdpwxNly6RXF/zvYA00H
wQIhAOVcL7gxNXF1xQIPabthI251/2H+A3kmfRu13WjU6yeBAiEApdXTyRMBZ70G
oq6Px9tzQ/cimt8exEW86l58QIsuC4ECIE7mJGvuOEDAnGW/tVQCzoUaYKKqco28
Nf0gQrzR6Uc6
-----END PRIVATE KEY-----
Ce contenu peut être affiché sous la forme alternative d'une structure définie selon la spécification mentionée à l'aide la commande asn1parse :
# openssl asn1parse -in key.prm
Ce qui produit :
 0:d=0  hl=4 l= 341 cons: SEQUENCE          
 4:d=1  hl=2 l=   1 prim: INTEGER           :00
 7:d=1  hl=2 l=  13 cons: SEQUENCE          
 9:d=2  hl=2 l=   9 prim: OBJECT            :rsaEncryption
20:d=2  hl=2 l=   0 prim: NULL              
22:d=1  hl=4 l= 319 prim: OCTET STRING      [HEX DUMP]:3082013B020100024100F38E99E6A0AD70FBE62231932044949C5BBFDFD890D5F7FBAFF7C882317D179A38F47898BE3BF997B6A0EEECC2E086435DA99EBDE5D1140EA133D0EC9DCE4A01020301000102402F8271A2FB54D9E5494F023CA0DEBFDE80473D73114DABC1FCE168CB7CBBDA0E69FBFA26E41E487669C3D7BE9321335D6942D16B977D2F3509E479E412080001022100FFA5CF3CF3254E9A08B08B18DF74409C27744AD73A546BF80883013467A3D241022100F3E486B2A80FB005F71AF915FE5750E51DDA70C4D972E915C5FF3BD8034D07C1022100E55C2FB831357175C5020F69BB61236E75FF61FE0379267D1BB5DD68D4EB2781022100A5D5D3C9130167BD06A2AE8FC7DB7343F7229ADF1EC445BCEA5E7C408B2E0B8102204EE6246BEE3840C09C65BFB55402CE851A60A2AA728DBC35FD2042BCD1E9473A
OpenSSL ne fait pas mieux en sortie, ce qui signifie qu'il n'est pas capable de générer un fichier au format ASN.1 tel que sa commande asn1parse lui permet de prendre en entrée.
En se basant sur la définition de la structure lue dans la RFC 5208, l'on en déduit qu'un tel fichier doit contenir :
asn1=SEQUENCE:PrivateKeyInfo
[PrivateKeyInfo]
version=INTEGER:00
privateKeyAlgorithm=SEQUENCE:privateKeyAlgorithmRSA
privateKey=FORMAT:HEX,OCTETSTRING:3082013B020100024100F38E99E6A0AD70FBE62231932044949C5BBFDFD890D5F7FBAFF7C882317D179A38F47898BE3BF997B6A0EEECC2E086435DA99EBDE5D1140EA133D0EC9DCE4A01020301000102402F8271A2FB54D9E5494F023CA0DEBFDE80473D73114DABC1FCE168CB7CBBDA0E69FBFA26E41E487669C3D7BE9321335D6942D16B977D2F3509E479E412080001022100FFA5CF3CF3254E9A08B08B18DF74409C27744AD73A546BF80883013467A3D241022100F3E486B2A80FB005F71AF915FE5750E51DDA70C4D972E915C5FF3BD8034D07C1022100E55C2FB831357175C5020F69BB61236E75FF61FE0379267D1BB5DD68D4EB2781022100A5D5D3C9130167BD06A2AE8FC7DB7343F7229ADF1EC445BCEA5E7C408B2E0B8102204EE6246BEE3840C09C65BFB55402CE851A60A2AA728DBC35FD2042BCD1E9473A
[privateKeyAlgorithmRSA]
algorithm=OID:rsaEncryption
parameter=NULL
Preuve que ASN.1 vise à décrire des structures, les noms utilisés pour désigner les structures et les champs n'ont aucune espèce d'importance pour l'usage qu'il s'agit de faire de ce fichier, à savoir le fournir en entrée à openssl. Ainsi, ce contenu est parfaitement équivalent :
asn1=SEQUENCE:Panoramix
[Panoramix]
Obelix=INTEGER:00
Asterix=SEQUENCE:Idefix
Assurancetourix=FORMAT:HEX,OCTETSTRING:3082013B020100024100F38E99E6A0AD70FBE62231932044949C5BBFDFD890D5F7FBAFF7C882317D179A38F47898BE3BF997B6A0EEECC2E086435DA99EBDE5D1140EA133D0EC9DCE4A01020301000102402F8271A2FB54D9E5494F023CA0DEBFDE80473D73114DABC1FCE168CB7CBBDA0E69FBFA26E41E487669C3D7BE9321335D6942D16B977D2F3509E479E412080001022100FFA5CF3CF3254E9A08B08B18DF74409C27744AD73A546BF80883013467A3D241022100F3E486B2A80FB005F71AF915FE5750E51DDA70C4D972E915C5FF3BD8034D07C1022100E55C2FB831357175C5020F69BB61236E75FF61FE0379267D1BB5DD68D4EB2781022100A5D5D3C9130167BD06A2AE8FC7DB7343F7229ADF1EC445BCEA5E7C408B2E0B8102204EE6246BEE3840C09C65BFB55402CE851A60A2AA728DBC35FD2042BCD1E9473A
[Idefix]
Agecanonix=OID:rsaEncryption
Abraracourcix=NULL
Noter que pour trouver la syntaxe pour OCTETSTRING, il faut assez longtemps partir à la pêche dans Google jusqu'à tomber sur ce précieux billet.
La commande asn1parse permet générer un DER uniquement. Pour obtenir un PEM, il faut procéder dans la foulée à une conversion :
# openssl asn1parse -genconf key.asn1 -out key.der -noout
# openssl pkcs8 -nocrypt -inform DER -in key.der -outform PEM -out key.pem
Cela permet de vérifier que c'est bien le même PEM :
-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA846Z5qCtcPvmIjGT
IESUnFu/39iQ1ff7r/fIgjF9F5o49HiYvjv5l7ag7uzC4IZDXameveXRFA6hM9Ds
nc5KAQIDAQABAkAvgnGi+1TZ5UlPAjyg3r/egEc9cxFNq8H84WjLfLvaDmn7+ibk
Hkh2acPXvpMhM11pQtFrl30vNQnkeeQSCAABAiEA/6XPPPMlTpoIsIsY33RAnCd0
Stc6VGv4CIMBNGej0kECIQDz5IayqA+wBfca+RX+V1DlHdpwxNly6RXF/zvYA00H
wQIhAOVcL7gxNXF1xQIPabthI251/2H+A3kmfRu13WjU6yeBAiEApdXTyRMBZ70G
oq6Px9tzQ/cimt8exEW86l58QIsuC4ECIE7mJGvuOEDAnGW/tVQCzoUaYKKqco28
Nf0gQrzR6Uc6
-----END PRIVATE KEY-----
Et les autres formats ? Distinguished Encoding Rules (DER) est un format binaire, et Privacy Enhanced Mail (PEM) est format texte, le résultat d'une simple conversion en base64 d'un DER.
Pour s'en convaincre, il suffit de reproduire la conversion manuellement, comme ici en Python :
import base64

file = open ('key.der', 'rb')
key = file.read()
file.close()
print (base64.b64encode(key))
Ce qui produit :
b'MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA846Z5qCtcPvmIjGTIESUnFu/39iQ1ff7r/fIgjF9F5o49HiYvjv5l7ag7uzC4IZDXameveXRFA6hM9Dsnc5KAQIDAQABAkAvgnGi+1TZ5UlPAjyg3r/egEc9cxFNq8H84WjLfLvaDmn7+ibkHkh2acPXvpMhM11pQtFrl30vNQnkeeQSCAABAiEA/6XPPPMlTpoIsIsY33RAnCd0Stc6VGv4CIMBNGej0kECIQDz5IayqA+wBfca+RX+V1DlHdpwxNly6RXF/zvYA00HwQIhAOVcL7gxNXF1xQIPabthI251/2H+A3kmfRu13WjU6yeBAiEApdXTyRMBZ70Goq6Px9tzQ/cimt8exEW86l58QIsuC4ECIE7mJGvuOEDAnGW/tVQCzoUaYKKqco28Nf0gQrzR6Uc6'
C'est bien ce qui se retrouve dans le PEM...
Hackflash #1 : Clé privée PKCS8 et formats ASN.1, DER et PEM