OpenSSL encryption, clé, iv, salt et magic number

 druide

Encrypt/Decrypt

Comment écrire un code source capable de décrypter un fichier encrypté avec l'utilitaire OpenSSL ? Et bien, c'est pas si simple... Commençons donc par le commencement. Comment encrypter un fichier avec OpenSSL.

L'utilitaire

OpenSSL nous fournit un utilitaire en ligne de commande qui permet l'encryption d'un fichier simplement. Avec l'option -p on peut même lui demander d'afficher le "sel", la clé et le vecteur d'initialisation utilisé

druide@druide:~$ openssl enc -aes-256-cbc -in fichier_a_crypter -out fichier_crypté -p
enter aes-256-cbc encryption password: Toto
Verifying - enter aes-256-cbc encryption password: Toto
salt=9E837D3CC8B67E5B
key=60E4931C9C2985F685AC23664AA195DD1E053323FB0A716173144F22C048C1BE
iv =E36D8EC00FBD8171F229C645FF7BB3D6
La vraie question c'est : pour pouvoir déchiffrer par code ce fichier, il faut que je sache comment l'utilitaire dérive la clé et IV de mon super mot de passe

Le code source d'OpenSSL

Dans le répertoire /apps des sources OpenSSL (version 1.0.1f sur Ubuntu 14.04), on trouve le fichier enc.c qui contient la dérivation du mot de passe et quelques surprises ☺.

341 if (dgst == NULL)
342 {
342 dgst = EVP_md5();
344 }

556 EVP_BytesToKey(cipher,dgst,sptr,
557 (unsigned char *)str,
558 strlen(str),1,key,iv);

Les surprises

  • Par défaut, le hash utilisé est le MD5, hors, The security of the MD5 hash function is severely compromised
  • On ne fait qu'un tour de boucle et ce n'est pas négociable alors que dans leur propre doc il préconise Increasing the count parameter slows down the algorithm which makes it harder for an attacker to perform a brute force attack using a large number of candidate passwords
On peut changer le hash utilisé avec l'option -md. On peut par exemple choisir un sha256. Par contre, pour changer le nombre de tour, il n'y a qu'une modification du code source suivie d'une re-compilation qui permette de le faire. J'ai donc choisi de recompiler le programme avec une valeur de 8164, j'ai ensuite encrypté un fichier en demandant à l'utilitaire OpenSSL de m'afficher le sel, la clé et le vecteur. Rem: le ./ devant openssl indique bien l'utilisation de l'utilitaire recompiler et pas celui installé dans la distribution

druide@druide:~$ ./openssl enc -p -aes-256-cbc -md sha256 -in ~/file.clear -out ~/file.enc
enter aes-256-cbc encryption password: Toto
Verifying - enter aes-256-cbc encryption password: Toto
salt=3AD48AAC890BE07F
key=4D8647196AD11BC61BEF39096C147D1FAE18F9426A52717E5DE08369836D8B92
iv =32D7116AD884056EE265A323B672643A


J'ai ensuite fait un programme C qui effectue le même travail de dérivation du mot de passe. Ça m'a permis de vérifier que j'étais capable de dériver les mêmes key/IV en connaissant le sel.
[collapsed title="Exemple C"]

#include #include #include


int main(int argc, char** argv)
{
unsigned char *str = "Toto";
unsigned char sptr[] = { 0x3A, 0xD4, 0x8A, 0xAC, 0x89, 0x0B, 0xE0, 0x7F };
const EVP_MD *dgst = NULL;
const EVP_CIPHER *cipher = NULL;
unsigned char key[EVP_MAX_KEY_LENGTH],iv[EVP_MAX_IV_LENGTH];
int i = 0;

OpenSSL_add_all_algorithms();

memset(key, 0, EVP_MAX_KEY_LENGTH);
memset(iv, 0, EVP_MAX_IV_LENGTH);
dgst = EVP_sha256();
cipher = EVP_get_cipherbyname("aes-256-cbc");

EVP_BytesToKey(cipher, dgst, sptr, (unsigned char *)str,
strlen(str), 8164, key, iv);

printf("salt=");

for(;i<8;i++)

{
printf("%02X", sptr[i]);
}

printf("\nkey=");

for(i=0;i<32;i++)

{
printf("%02X", key[i]);
}

printf("\niv=");

for(i = 0;i<16;i++)

{
printf("%02X", iv[i]);
}

printf("\nEnd\n");

return 0;
}

[/collapse]

Compilation et essai


druide@druide:~$ gcc -o essai essai.c `pkg-config --cflags --libs openssl`
druide@druide:~$ ./essai 
salt=3AD48AAC890BE07F
key=4D8647196AD11BC61BEF39096C147D1FAE18F9426A52717E5DE08369836D8B92
iv=32D7116AD884056EE265A323B672643A
End

La grosse surprise

Mais on n'est pas au bout de nos surprises. En regardant de plus près le fichier encrypté, on y trouve... on y trouve...

LE SEL!

druide@druide:~$ head -c 16 file.enc | hd
00000000  53 61 6c 74 65 64 5f 5f  3a d4 8a ac 89 0b e0 7f  |Salted__:.......|
00000010




Non, c'est normal. Monstrueux mais normal. En effet, quand OpenSSL voudra déchiffrer le fichier, il aura besoin du sel utilisé lors du chiffrement pour dériver les mêmes key/IV en partant du mot de passe. Il place donc un entête dans le fichier contenant le texte "Salted__" suivit du sel. Le tout faisant 16 octets.

Cependant, stocker le sel dans le fichier... autant pas saler 😏. Dans ce cas, la sécurité repose uniquement sur la force du mot de passe. Si en plus, le hash est laissé par défaut, je ne donne pas cher du cryptage.

Conclusion

Chaque domaine se spécialisant de plus en plus, il faut bien creuser le sujet et ne pas faire aveuglement confiance.

Si on est au clair sur les principes de base, on peut déjà éviter les pièges grossiers.

Dans le cas d'OpenSSL, que dire. Si on veut l'utiliser, il faut le recompiler et nettoyer l'entête du fichier crypté. Stocker précieusement le sel pour chaque fichier et recomposer le fichier pour pouvoir le décrypter.

Pour ma part, je vais faire mon utilitaire qui utilise la librairie OpenSSL ou alors, pourquoi pas, passer à GnuPG.

Mais GnuPG... Est-ce qu'il ne fait pas aussi quelques surprises inattendues...

  • 3 years 8 months before
  • |