Déc 312014
 

Le but de cet article est de montrer comment une clef privée ssh est chiffrée/déchiffrée par une passphrase.

Pour les besoins de cette démonstration, on génère une clef RSA de 768 bits. La taille de la clef RSA est petite pour faciliter la manipulation des données.

ssh-keygen -t rsa -b 768 -f /root/ssh/id_rsa

  • -t rsa : l’algorithme de la clef privée est RSA
  • -b 768 : taille en bits de la clef (du modulus)
  • -f /root/ssh/id_rsa : emplacement et nom de la clef RSA générée

ssh-keygen -t rsa -b 768 -f /root/ssh/id_rsa

  • Enter passphrase (empty for no passphrase):12345678
  • Enter same passphrase again: 12345678
  • Your identification has been saved in /root/ssh/id_rsa.
  • Your public key has been saved in /root/ssh/id_rsa.pub.
  • The key fingerprint is: b8:ec:0e:3a:cc:83:4e:6c:1f:6a:fc:89:bb:59:49:69

La passphrase est : 12345678

pour info : le « key fingerprint » est un hash md5 de la clef publique.

Deux méthodes permettent de chiffrer la clef privée :

1ère méthode :

  • Transformation de la passphrase : md5 (passphrase || salt )
  • Déchiffrement AES

2ème méthode

  • Transformation de la passphrase : fonction pkbdf2 (salt, passphrase et itération)
  • Déchiffrement 3DES (3 clefs de 8 octets différentes)

une 3ème méthode est apparue dans les versions récentes d’openssh. Elle n’ est pas implémentée pour le moment dans les distributions classiques (debian, centos).

Dans la suite de cet article , nous allons décrire les 2 méthodes.

Première méthode

Editons la clef privée RSA :

cat /root/ssh/id_rsa
pour copier le code, double-cliquer sur le code et dans le menu contextuel, sélectionner copier

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,9F0EF3706D32905A4E2915B4D2FBE907

qEGhfPtWHeTZbEHGqyWr+jL270jD3wRbn6kj4Ojerib1PVz4WVBV2TCM4Ha/5i3U
Fo0W+3R+qdkPLjoek8IQTh47nTfzWBnF/aX/HOpk2sv4ytzPuCpIK4Lhe8cETS63
1trncuxAvVJ96Mz2EKRLpS2DtUWxRo8qp4nVlcNQEGNfbUjUTKrd3vSRhTaidw32
gOcF2zO0hK5Hesttykh7TlLj3f1V1F43S/pcmnlGqyFw8potz39eJnqfsOFWOFUr
hr0LEHN0yXZOENSYHzH6OYjmWcRwz3nHHYftDxWJXEAa2CtAIRG9haip+HGPJqA4
BOIJtfWNnkRn5nMaijSuDnuqkRgORMB4ca/otKYFnF3iSBZOBuEDOCE8r8m8FxNV
TtzEZTL43JfRMQF+AdZnOujbDvj4wDu6WtSszZ1QljTT/Qirt69dEofoiipFdpPH
o6i53KU6/NEY7gQm1+cqcshRGvl0t48WcbtNcn1Fh3LxVwCw7Rq4AmrKVQNPGLxo
wB9d+n3hLL/pyZKUux14TLI6o2NOip5T/0mcuxxiH3f9dsdDqzvy5m4NvLy2BRYV
AVUc4fJfXPixw/BRBOqm5AD2jnyXpS8u4xqF29KfZu4wG2w9Sl0nWO3xHcpwdACy
-----END RSA PRIVATE KEY-----

L’ entête permet d’obtenir certains renseignements

—–BEGIN RSA PRIVATE KEY—– : contient une clef privée rsa
Proc-Type: 4,ENCRYPTED : la clef privée est cryptée

Décomposons la ligne suivante :
DEK-Info: AES-128-CBC, 9F0EF3706D32905A4E2915B4D2FBE907

  • DEK-Info : (Data Encryption Key)
  • AES-128-CBC : la clef est chiffrée en AES128 mode CBC
  • 9F0EF3706D32905A4E2915B4D2FBE907 : iv (initial vector) – utilisé par le mode CBC

Décodage base 64

Programme python pour décoder le base 64

import base64
import textwrap
 
ChaineBase64= \
"qEGhfPtWHeTZbEHGqyWr+jL270jD3wRbn6kj4Ojerib1PVz4WVBV2TCM4Ha/5i3U" + \
"Fo0W+3R+qdkPLjoek8IQTh47nTfzWBnF/aX/HOpk2sv4ytzPuCpIK4Lhe8cETS63" + \
"1trncuxAvVJ96Mz2EKRLpS2DtUWxRo8qp4nVlcNQEGNfbUjUTKrd3vSRhTaidw32" + \
"gOcF2zO0hK5Hesttykh7TlLj3f1V1F43S/pcmnlGqyFw8potz39eJnqfsOFWOFUr" + \
"hr0LEHN0yXZOENSYHzH6OYjmWcRwz3nHHYftDxWJXEAa2CtAIRG9haip+HGPJqA4" + \
"BOIJtfWNnkRn5nMaijSuDnuqkRgORMB4ca/otKYFnF3iSBZOBuEDOCE8r8m8FxNV" + \
"TtzEZTL43JfRMQF+AdZnOujbDvj4wDu6WtSszZ1QljTT/Qirt69dEofoiipFdpPH" + \
"o6i53KU6/NEY7gQm1+cqcshRGvl0t48WcbtNcn1Fh3LxVwCw7Rq4AmrKVQNPGLxo" + \
"wB9d+n3hLL/pyZKUux14TLI6o2NOip5T/0mcuxxiH3f9dsdDqzvy5m4NvLy2BRYV" + \
"AVUc4fJfXPixw/BRBOqm5AD2jnyXpS8u4xqF29KfZu4wG2w9Sl0nWO3xHcpwdACy"
 
StringHexa=base64.decodestring(ChaineBase64).encode('hex')
 
# decoupage 32 octets par ligne
print '\n'.join(textwrap.wrap(''.join(textwrap.wrap(StringHexa,2)),64))

Résultat:

a841a17cfb561de4d96c41c6ab25abfa32f6ef48c3df045b9fa923e0e8deae26
f53d5cf8595055d9308ce076bfe62dd4168d16fb747ea9d90f2e3a1e93c2104e
1e3b9d37f35819c5fda5ff1cea64dacbf8cadccfb82a482b82e17bc7044d2eb7
d6dae772ec40bd527de8ccf610a44ba52d83b545b1468f2aa789d595c3501063
5f6d48d44caadddef4918536a2770df680e705db33b484ae477acb6dca487b4e
52e3ddfd55d45e374bfa5c9a7946ab2170f29a2dcf7f5e267a9fb0e15638552b
86bd0b107374c9764e10d4981f31fa3988e659c470cf79c71d87ed0f15895c40
1ad82b402111bd85a8a9f8718f26a03804e209b5f58d9e4467e6731a8a34ae0e
7baa91180e44c07871afe8b4a6059c5de248164e06e10338213cafc9bc171355
4edcc46532f8dc97d131017e01d6673ae8db0ef8f8c03bba5ad4accd9d509634
d3fd08abb7af5d1287e88a2a457693c7a3a8b9dca53afcd118ee0426d7e72a72
c8511af974b78f1671bb4d727d458772f15700b0ed1ab8026aca55034f18bc68
c01f5dfa7de12cbfe9c99294bb1d784cb23aa3634e8a9e53ff499cbb1c621f77
fd76c743ab3bf2e66e0dbcbcb605161501551ce1f25f5cf8b1c3f05104eaa6e4
00f68e7c97a52f2ee31a85dbd29f66ee301b6c3d4a5d2758edf11dca707400b2

Paramètres connus :
Passphrase : "12345678"

  • DEK-Info :
  • AES-128-CBC : information sur le chiffrement
  • 9F0EF3706D32905A4E2915B4D2FBE907 : iv (initial vector)

Transformation de la passphrase en une clef AES

Tout d’abord , transformation de la passphrase en une clef.

La passphrase est salée et hashée pour obtenir la véritable clef de chiffrement

Concaténer le mot de passe aux 8 premiers octets de poids fort de l’ IV :
"12345678" || 9F0EF3706D32905A

Dans un éditeur hexa vous devez obtenir ceci :

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  31 32 33 34 35 36 37 38 9F 0E F3 70 6D 32 90 5A 12345678Ÿ.ópm2.Z

Hasher en md5 la concaténation

md5("12345678" || 9F0EF3706D32905A)=01BF2442690BFCFA11D44BAABFBB2643

01BF2442690BFCFA11D44BAABFBB2643 est la véritable clef qui va servir à déchiffrer la clef privée RSA

Déchiffrement de la clef privée rsa

Nous disposons maintenant des éléments nécessaires pour déchiffrer la clef privée RSA

La clef
01BF2442690BFCFA11D44BAABFBB2643

Le vecteur d’ initialisation (iv)
9F0EF3706D32905A4E2915B4D2FBE907

La clef chiffrée RSA en AES mode CBC

a841a17cfb561de4d96c41c6ab25abfa32f6ef48c3df045b9fa923e0e8deae26
f53d5cf8595055d9308ce076bfe62dd4168d16fb747ea9d90f2e3a1e93c2104e
1e3b9d37f35819c5fda5ff1cea64dacbf8cadccfb82a482b82e17bc7044d2eb7
d6dae772ec40bd527de8ccf610a44ba52d83b545b1468f2aa789d595c3501063
5f6d48d44caadddef4918536a2770df680e705db33b484ae477acb6dca487b4e
52e3ddfd55d45e374bfa5c9a7946ab2170f29a2dcf7f5e267a9fb0e15638552b
86bd0b107374c9764e10d4981f31fa3988e659c470cf79c71d87ed0f15895c40
1ad82b402111bd85a8a9f8718f26a03804e209b5f58d9e4467e6731a8a34ae0e
7baa91180e44c07871afe8b4a6059c5de248164e06e10338213cafc9bc171355
4edcc46532f8dc97d131017e01d6673ae8db0ef8f8c03bba5ad4accd9d509634
d3fd08abb7af5d1287e88a2a457693c7a3a8b9dca53afcd118ee0426d7e72a72
c8511af974b78f1671bb4d727d458772f15700b0ed1ab8026aca55034f18bc68
c01f5dfa7de12cbfe9c99294bb1d784cb23aa3634e8a9e53ff499cbb1c621f77
fd76c743ab3bf2e66e0dbcbcb605161501551ce1f25f5cf8b1c3f05104eaa6e4
00f68e7c97a52f2ee31a85dbd29f66ee301b6c3d4a5d2758edf11dca707400b2

Programme en python pour déchiffrer la clef privée

import textwrap
from Crypto.Cipher import AES
key="\x01\xBF\x24\x42\x69\x0B\xFC\xFA\x11\xD4\x4B\xAA\xBF\xBB\x26\x43"
iv ="\x9F\x0E\xF3\x70\x6D\x32\x90\x5A\x4E\x29\x15\xB4\xD2\xFB\xE9\x07"
msg=\
"\xa8\x41\xa1\x7c\xfb\x56\x1d\xe4\xd9\x6c\x41\xc6\xab\x25\xab\xfa" + \
"\x32\xf6\xef\x48\xc3\xdf\x04\x5b\x9f\xa9\x23\xe0\xe8\xde\xae\x26" + \
"\xf5\x3d\x5c\xf8\x59\x50\x55\xd9\x30\x8c\xe0\x76\xbf\xe6\x2d\xd4" + \
"\x16\x8d\x16\xfb\x74\x7e\xa9\xd9\x0f\x2e\x3a\x1e\x93\xc2\x10\x4e" + \
"\x1e\x3b\x9d\x37\xf3\x58\x19\xc5\xfd\xa5\xff\x1c\xea\x64\xda\xcb" + \
"\xf8\xca\xdc\xcf\xb8\x2a\x48\x2b\x82\xe1\x7b\xc7\x04\x4d\x2e\xb7" + \
"\xd6\xda\xe7\x72\xec\x40\xbd\x52\x7d\xe8\xcc\xf6\x10\xa4\x4b\xa5" + \
"\x2d\x83\xb5\x45\xb1\x46\x8f\x2a\xa7\x89\xd5\x95\xc3\x50\x10\x63" + \
"\x5f\x6d\x48\xd4\x4c\xaa\xdd\xde\xf4\x91\x85\x36\xa2\x77\x0d\xf6" + \
"\x80\xe7\x05\xdb\x33\xb4\x84\xae\x47\x7a\xcb\x6d\xca\x48\x7b\x4e" + \
"\x52\xe3\xdd\xfd\x55\xd4\x5e\x37\x4b\xfa\x5c\x9a\x79\x46\xab\x21" + \
"\x70\xf2\x9a\x2d\xcf\x7f\x5e\x26\x7a\x9f\xb0\xe1\x56\x38\x55\x2b" + \
"\x86\xbd\x0b\x10\x73\x74\xc9\x76\x4e\x10\xd4\x98\x1f\x31\xfa\x39" + \
"\x88\xe6\x59\xc4\x70\xcf\x79\xc7\x1d\x87\xed\x0f\x15\x89\x5c\x40" + \
"\x1a\xd8\x2b\x40\x21\x11\xbd\x85\xa8\xa9\xf8\x71\x8f\x26\xa0\x38" + \
"\x04\xe2\x09\xb5\xf5\x8d\x9e\x44\x67\xe6\x73\x1a\x8a\x34\xae\x0e" + \
"\x7b\xaa\x91\x18\x0e\x44\xc0\x78\x71\xaf\xe8\xb4\xa6\x05\x9c\x5d" + \
"\xe2\x48\x16\x4e\x06\xe1\x03\x38\x21\x3c\xaf\xc9\xbc\x17\x13\x55" + \
"\x4e\xdc\xc4\x65\x32\xf8\xdc\x97\xd1\x31\x01\x7e\x01\xd6\x67\x3a" + \
"\xe8\xdb\x0e\xf8\xf8\xc0\x3b\xba\x5a\xd4\xac\xcd\x9d\x50\x96\x34" + \
"\xd3\xfd\x08\xab\xb7\xaf\x5d\x12\x87\xe8\x8a\x2a\x45\x76\x93\xc7" + \
"\xa3\xa8\xb9\xdc\xa5\x3a\xfc\xd1\x18\xee\x04\x26\xd7\xe7\x2a\x72" + \
"\xc8\x51\x1a\xf9\x74\xb7\x8f\x16\x71\xbb\x4d\x72\x7d\x45\x87\x72" + \
"\xf1\x57\x00\xb0\xed\x1a\xb8\x02\x6a\xca\x55\x03\x4f\x18\xbc\x68" + \
"\xc0\x1f\x5d\xfa\x7d\xe1\x2c\xbf\xe9\xc9\x92\x94\xbb\x1d\x78\x4c" + \
"\xb2\x3a\xa3\x63\x4e\x8a\x9e\x53\xff\x49\x9c\xbb\x1c\x62\x1f\x77" + \
"\xfd\x76\xc7\x43\xab\x3b\xf2\xe6\x6e\x0d\xbc\xbc\xb6\x05\x16\x15" + \
"\x01\x55\x1c\xe1\xf2\x5f\x5c\xf8\xb1\xc3\xf0\x51\x04\xea\xa6\xe4" + \
"\x00\xf6\x8e\x7c\x97\xa5\x2f\x2e\xe3\x1a\x85\xdb\xd2\x9f\x66\xee" + \
"\x30\x1b\x6c\x3d\x4a\x5d\x27\x58\xed\xf1\x1d\xca\x70\x74\x00\xb2"
  
cipher = AES.new(key, AES.MODE_CBC,iv)
d = cipher.decrypt(msg)
# decoupage en ligne de 32 octets
print '\n'.join(textwrap.wrap(''.join(textwrap.wrap(d.encode('hex'),2)),64))

Clef privée RSA déchiffrée.

308201cc020100026100e11052645abad97049b654b4ad957da431ef10d89b9c
205eb5c19d1e18d352ef8bf1ab067bfd2f7b923e953a00d81a70584813af4d1a
bbf3b49888634d52a57c50ed17ef7effb8e6d5a3776c268c35a698d04248fe8c
50ea35b8f8a8ba93496d
0203010001026100982904cb3c5c1f2dbf683a147d57
183eae53c6bbeb58beb9c98040cd5c53633af90e466677e550f8e682d13909e1
10dda6a2baf19ce170d2ee344eadef89e6d34e047fa5317accf15ed81136fd89
bee299792473b2fad70a6a12077466330281
023100fbb93cdb6ef0c15cea749c
27744beac5d9d13b9cfabaa22dc950faa071b16384cb343c98020a67c4b4e156
ea870e4c89
023100e4e32387e734b14768bb68f35be78fe780784b79ea12dacb
8fad32978775ea7d2e89ccb7ceafd7f7c2a2f9eb9ace44c5
023100efc545039b
4bf65323fc491587c01c73b3038aedff21b1033de445a67845488f1fe3b4e7f1
e1a97003ff3484167af749
023100e4a48daac5e89a2b0651281eea30e04cd6c5
d447bdef05c79107e7e0fcbd6af78fd4b1beef66332c59af2a4f13fe772d
0230
02a7abe0f3d78a4b8183a7135fa1c22a8795898fad22c4cc514ce4bfb1ba6bb7
6152453771cc4f53b2d49d3db8c89233
10101010101010101010101010101010


en bleu : m (modulus de 00 E1 à 49 6D)
en bleu : e (exposant public 0x10001 = 65537d)
en vert : d (exposant privé de 00 98 à 02 81)
en vert : p (de 00 FB à 4C 89)
en vert : q (de 00 E4 à 44 C5)
en gris : d mod (p-1)
en gris : d mod (q-1)
en gris : q^-1 mod p => modular inverse de q

On remarque que le résultat obtenu est au format asn.1


ssh-passphrase-asn1-01

Un brute force ou une attaque par dictionnaire de la passphrase est facilité puisqu’on utilise l’ algorithme de hashage md5 et un chiffrement AES qui sont des algorithmes optimisés en temps de calcul.
Donc on va pouvoir tester un grand nombre de combinaisons à la seconde.

Deuxième méthode

Comment diminuer le nombre de combinaisons à la seconde, en d’ autres termes, augmenter les temps de calcul ?

Solution :
Itérer le hashage et utiliser des algorithmes moins optimisés.

C’ est ce que propose la deuxième méthode basée sur pbkdf2 et le chiffrement 3des (temps de calcul plus long)

Avec cette méthode, il n’ est pas possible de générer une clef rsa et de la chiffrer immédiatement .

Il faut créer la clef privée de manière classique et ensuite la convertir

Créer la clef privée

  • ssh-keygen -t rsa -b 768 -f /root/ssh/id_rsa

Renommer la clef privée en id_rsa.ori

  • mv id_rsa id_rsa.ori

Conversion au nouveau format de chiffrement

  • openssl pkcs8 -topk8 -v2 des3 -in id_rsa.ori -out id_rsa

Comparaison des contenus entre les 2 méthodes de chiffrement des clefs RSA

1ère méthode :
cat /root/ssh/id_rsa

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,9F0EF3706D32905A4E2915B4D2FBE907

qEGhfPtWHeTZbEHGqyWr+jL270jD3wRbn6kj4Ojerib1PVz4WVBV2TCM4Ha/5i3U
Fo0W+3R+qdkPLjoek8IQTh47nTfzWBnF/aX/HOpk2sv4ytzPuCpIK4Lhe8cETS63
1trncuxAvVJ96Mz2EKRLpS2DtUWxRo8qp4nVlcNQEGNfbUjUTKrd3vSRhTaidw32
gOcF2zO0hK5Hesttykh7TlLj3f1V1F43S/pcmnlGqyFw8potz39eJnqfsOFWOFUr
hr0LEHN0yXZOENSYHzH6OYjmWcRwz3nHHYftDxWJXEAa2CtAIRG9haip+HGPJqA4
BOIJtfWNnkRn5nMaijSuDnuqkRgORMB4ca/otKYFnF3iSBZOBuEDOCE8r8m8FxNV
TtzEZTL43JfRMQF+AdZnOujbDvj4wDu6WtSszZ1QljTT/Qirt69dEofoiipFdpPH
o6i53KU6/NEY7gQm1+cqcshRGvl0t48WcbtNcn1Fh3LxVwCw7Rq4AmrKVQNPGLxo
wB9d+n3hLL/pyZKUux14TLI6o2NOip5T/0mcuxxiH3f9dsdDqzvy5m4NvLy2BRYV
AVUc4fJfXPixw/BRBOqm5AD2jnyXpS8u4xqF29KfZu4wG2w9Sl0nWO3xHcpwdACy
-----END RSA PRIVATE KEY-----

2ème méthode
cat /root/ssh/id_rsa

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICNjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIHHjXb91etI4CAggA
MBQGCCqGSIb3DQMHBAhpIc3PTdDu5wSCAfCWn6sVP0VZfwAcAJb0Q7cGvbaRmdmy
dcxmMsFQMhaNLkOD8jo2rdzSh3rqPTzX7QZrbh9eqQgezNg/tTTAbHxMg4WZEpFy
/ViH64+uNsMfb/pAwrsWKiPi7/9dCiqhHC0VuswP9/scvVU2AM7NnJoelCXaUXD+
SBF/qwqyXVu8YmBphMpgzMMOP/guGQ3FAumIeANXPD/N1Zin7RUejK9WQAt16FNT
ezeBaV14mBHYwZ8dEC1Yc0/ytcJBKTa5HamqXHJwlQeN7MW5QQ276xXBcLBNo+sJ
rgVsm/WYBFFET1wsrMlFm4e9PZP3AyGAq7XqlY3lI7UW4wJlmng+dDtt4Jij7ccD
BsEyC3UQeWccchMSy3zcfE17LFKqwJDROtcbY7oYDJg6mzfn5gcICVH3RMPdZnZH
pZ5b7jitIA30wV3yjWqLK3A8HM5NKr4QIpMTNxMclm6lROkZqTSNkz7H0xVL2w4j
INQmx/1gLVa4pK8ADFtzlBdwM/5pztQkYW8CvKdwcZFiiDIcZv9N2J/8+fRjV1k3
tPsrgPmaiySDIIl4TKfW/KhKvCGlioTc3M5MfupE3D3PY3vo7z+4tBYK5RlPOUmR
FvwKngyP8ypzRv+cgk2MJNahk8qD7lH33AHhi1kzrk3+Fcr5mZD9r1aL
-----END ENCRYPTED PRIVATE KEY-----

Remarques :

-----BEGIN RSA PRIVATE KEY----- devient -----BEGIN ENCRYPTED PRIVATE KEY-----

il n’y a plus d’entête

Décodage base 64

Programme python pour décoder le base 64

import base64
import textwrap
 
ChaineBase64= \
"MIICNjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIHHjXb91etI4CAggA" + \
"MBQGCCqGSIb3DQMHBAhpIc3PTdDu5wSCAfCWn6sVP0VZfwAcAJb0Q7cGvbaRmdmy" + \
"dcxmMsFQMhaNLkOD8jo2rdzSh3rqPTzX7QZrbh9eqQgezNg/tTTAbHxMg4WZEpFy" + \
"/ViH64+uNsMfb/pAwrsWKiPi7/9dCiqhHC0VuswP9/scvVU2AM7NnJoelCXaUXD+" + \
"SBF/qwqyXVu8YmBphMpgzMMOP/guGQ3FAumIeANXPD/N1Zin7RUejK9WQAt16FNT" + \
"ezeBaV14mBHYwZ8dEC1Yc0/ytcJBKTa5HamqXHJwlQeN7MW5QQ276xXBcLBNo+sJ" + \
"rgVsm/WYBFFET1wsrMlFm4e9PZP3AyGAq7XqlY3lI7UW4wJlmng+dDtt4Jij7ccD" + \
"BsEyC3UQeWccchMSy3zcfE17LFKqwJDROtcbY7oYDJg6mzfn5gcICVH3RMPdZnZH" + \
"pZ5b7jitIA30wV3yjWqLK3A8HM5NKr4QIpMTNxMclm6lROkZqTSNkz7H0xVL2w4j" + \
"INQmx/1gLVa4pK8ADFtzlBdwM/5pztQkYW8CvKdwcZFiiDIcZv9N2J/8+fRjV1k3" + \
"tPsrgPmaiySDIIl4TKfW/KhKvCGlioTc3M5MfupE3D3PY3vo7z+4tBYK5RlPOUmR" + \
"FvwKngyP8ypzRv+cgk2MJNahk8qD7lH33AHhi1kzrk3+Fcr5mZD9r1aL"
 
StringHexa=base64.decodestring(ChaineBase64).encode('hex')
 
print '\n'.join(textwrap.wrap(''.join(textwrap.wrap(StringHexa,2)),64))

Résultat

30820236304006092a864886f70d01050d3033301b06092a864886f70d01050c
300e04081c78d76fdd5eb48e02020800301406082a864886f70d030704086921
cdcf4dd0eee7048201f0969fab153f45597f001c0096f443b706bdb69199d9b2
75cc6632c15032168d2e4383f23a36addcd2877aea3d3cd7ed066b6e1f5ea908
1eccd83fb534c06c7c4c838599129172fd5887eb8fae36c31f6ffa40c2bb162a
23e2efff5d0a2aa11c2d15bacc0ff7fb1cbd553600cecd9c9a1e9425da5170fe
48117fab0ab25d5bbc62606984ca60ccc30e3ff82e190dc502e9887803573c3f
cdd598a7ed151e8caf56400b75e853537b3781695d789811d8c19f1d102d5873
4ff2b5c2412936b91da9aa5c727095078decc5b9410dbbeb15c170b04da3eb09
ae056c9bf5980451444f5c2cacc9459b87bd3d93f7032180abb5ea958de523b5
16e302659a783e743b6de098a3edc70306c1320b751079671c721312cb7cdc7c
4d7b2c52aac090d13ad71b63ba180c983a9b37e7e607080951f744c3dd667647
a59e5bee38ad200df4c15df28d6a8b2b703c1cce4d2abe1022931337131c966e
a544e919a9348d933ec7d3154bdb0e2320d426c7fd602d56b8a4af000c5b7394
177033fe69ced424616f02bca77071916288321c66ff4dd89ffcf9f463575937
b4fb2b80f99a8b24832089784ca7d6fca84abc21a58a84dcdcce4c7eea44dc3d
cf637be8ef3fb8b4160ae5194f39499116fc0a9e0c8ff32a7346ff9c824d8c24
d6a193ca83ee51f7dc01e18b5933ae4dfe15caf99990fdaf568b

La clef privée chiffrée est au format asn.1

30820236304006092a864886f70d01050d3033301b06092a864886f70d01050c
300e04081c78d76fdd5eb48e02020800301406082a864886f70d030704086921
cdcf4dd0eee7048201f0969fab153f45597f001c0096f443b706bdb69199d9b2
75cc6632c15032168d2e4383f23a36addcd2877aea3d3cd7ed066b6e1f5ea908
1eccd83fb534c06c7c4c838599129172fd5887eb8fae36c31f6ffa40c2bb162a
23e2efff5d0a2aa11c2d15bacc0ff7fb1cbd553600cecd9c9a1e9425da5170fe
48117fab0ab25d5bbc62606984ca60ccc30e3ff82e190dc502e9887803573c3f
cdd598a7ed151e8caf56400b75e853537b3781695d789811d8c19f1d102d5873
4ff2b5c2412936b91da9aa5c727095078decc5b9410dbbeb15c170b04da3eb09
ae056c9bf5980451444f5c2cacc9459b87bd3d93f7032180abb5ea958de523b5
16e302659a783e743b6de098a3edc70306c1320b751079671c721312cb7cdc7c
4d7b2c52aac090d13ad71b63ba180c983a9b37e7e607080951f744c3dd667647
a59e5bee38ad200df4c15df28d6a8b2b703c1cce4d2abe1022931337131c966e
a544e919a9348d933ec7d3154bdb0e2320d426c7fd602d56b8a4af000c5b7394
177033fe69ced424616f02bca77071916288321c66ff4dd89ffcf9f463575937
b4fb2b80f99a8b24832089784ca7d6fca84abc21a58a84dcdcce4c7eea44dc3d
cf637be8ef3fb8b4160ae5194f39499116fc0a9e0c8ff32a7346ff9c824d8c24
d6a193ca83ee51f7dc01e18b5933ae4dfe15caf99990fdaf568b

En noir : Les paramètres permettant de déchiffrer la clef RSA
En bleu : La clef RSA chiffrée


ssh-passphrase-asn1-03

Description des paramètres :
1.2.840.113549.1.5.13 (PBES2: PBES2 encryption scheme) (rfc 2898)
1.2.840.113549.1.5.12 (PBKDF2 – Key Derivation Function) (rfc 2898)

1C78D76FDD5EB48E (salt)
0x0800 (2048) (iteration count 2048)

1.2.840.113549.3.7 (DES3 encryption in CBC mode)
6921CDCF4DD0EEE7 (vecteur d’initialisation pour le mode cbc)

969fab153f45597f001c0096f443b706bdb69199d9b2 … (clef privée RSA chiffrée)

Un paramètre n’ apparaît pas dans cette liste, il s’ agit de la spécification de l’algorithme de hashage utilisé par pbkdf2 avec ssh. pbkdf2 utilise hmac-sha1.

Transformation de la passphrase en une clef 3des

La passphrase est salée, hashée et itérée pour obtenir la véritable clef de dé(chiffrement)
Le programme python utilise l’ algorithme pbkdf2 pour calculer la clef de dé(chiffrement)

Pbkdf2 permet de générer à partir de la passphrase une taille de clef de son choix.

On a besoin d’une clef de 24 octets puisqu’on utilise l’ algorithme symétrique 3DES (3 * 8 octets)

import struct
import hashlib
import hmac
import binascii
 
# fonctions
 
def hmacfunc(key,message):
    # key = mot de passe
    # message = salt
    return hmac.new(key,message,hashlib.sha1).digest()
 
def xor_string(str1, str2):
    # TODO: slow!
    str3 = ''
    for i in xrange(len(str1)):
        str3 += chr(ord(str1[i]) ^ ord(str2[i]))
    return str3
 
def PBKDF2(password, salt, iterations, derivedlen):
 
        def F(P, S, c, i):
            U_prev = hmacfunc(P, S + struct.pack('>L', i))
            res = U_prev
            for cc in xrange(2, c+1):
                U_c = hmacfunc(P, U_prev)
                res = xor_string(res, U_c)
                U_prev = U_c
            return res
        tmp = ''
        i = 1
        while True:
            tmp += str(F(password, salt, iterations, i))
            if len(tmp) > derivedlen:
                break
            i += 1
 
        return tmp[0:(derivedlen)]
 
#arg01 : mot de pase ("12345678")
#arg02 : salt        (
#arg03 : iteration
#arg04 : taille en octets du resultat
resultat =  PBKDF2("12345678", "\x1C\x78\xD7\x6F\xDD\x5E\xB4\x8E", 2048, 24)
 
print "resultat " + binascii.hexlify(resultat).upper()

Déchiffrement de la clef privée rsa

Nous disposons maintenant des éléments nécessaires pour déchiffrer la clef privée RSA
La clef 3DES est : 95A8B6680BB3EE39E183836CAB7D4DA6057032B1A25C54E4

Le vecteur d’initialisation est : 6921CDCF4DD0EEE7

La clef privée RSA chiffrée est :

969fab153f45597f001c0096f443b706bdb69199d9b275cc6632c15032168d2e
4383f23a36addcd2877aea3d3cd7ed066b6e1f5ea9081eccd83fb534c06c7c4c
838599129172fd5887eb8fae36c31f6ffa40c2bb162a23e2efff5d0a2aa11c2d
15bacc0ff7fb1cbd553600cecd9c9a1e9425da5170fe48117fab0ab25d5bbc62
606984ca60ccc30e3ff82e190dc502e9887803573c3fcdd598a7ed151e8caf56
400b75e853537b3781695d789811d8c19f1d102d58734ff2b5c2412936b91da9
aa5c727095078decc5b9410dbbeb15c170b04da3eb09ae056c9bf5980451444f
5c2cacc9459b87bd3d93f7032180abb5ea958de523b516e302659a783e743b6d
e098a3edc70306c1320b751079671c721312cb7cdc7c4d7b2c52aac090d13ad7
1b63ba180c983a9b37e7e607080951f744c3dd667647a59e5bee38ad200df4c1
5df28d6a8b2b703c1cce4d2abe1022931337131c966ea544e919a9348d933ec7
d3154bdb0e2320d426c7fd602d56b8a4af000c5b7394177033fe69ced424616f
02bca77071916288321c66ff4dd89ffcf9f463575937b4fb2b80f99a8b248320
89784ca7d6fca84abc21a58a84dcdcce4c7eea44dc3dcf637be8ef3fb8b4160a
e5194f39499116fc0a9e0c8ff32a7346ff9c824d8c24d6a193ca83ee51f7dc01
e18b5933ae4dfe15caf99990fdaf568b

Programme en python pour déchiffrer la clef privée

from Crypto.Cipher import DES3
import textwrap
 
key="\x95\xA8\xB6\x68\x0B\xB3\xEE\x39" + \
    "\xE1\x83\x83\x6C\xAB\x7D\x4D\xA6" + \
    "\x05\x70\x32\xB1\xA2\x5C\x54\xE4"
 
iv ="\x69\x21\xCD\xCF\x4D\xD0\xEE\xE7"
 
msg=\
"\x96\x9f\xab\x15\x3f\x45\x59\x7f\x00\x1c\x00\x96\xf4\x43\xb7\x06" + \
"\xbd\xb6\x91\x99\xd9\xb2\x75\xcc\x66\x32\xc1\x50\x32\x16\x8d\x2e" + \
"\x43\x83\xf2\x3a\x36\xad\xdc\xd2\x87\x7a\xea\x3d\x3c\xd7\xed\x06" + \
"\x6b\x6e\x1f\x5e\xa9\x08\x1e\xcc\xd8\x3f\xb5\x34\xc0\x6c\x7c\x4c" + \
"\x83\x85\x99\x12\x91\x72\xfd\x58\x87\xeb\x8f\xae\x36\xc3\x1f\x6f" + \
"\xfa\x40\xc2\xbb\x16\x2a\x23\xe2\xef\xff\x5d\x0a\x2a\xa1\x1c\x2d" + \
"\x15\xba\xcc\x0f\xf7\xfb\x1c\xbd\x55\x36\x00\xce\xcd\x9c\x9a\x1e" + \
"\x94\x25\xda\x51\x70\xfe\x48\x11\x7f\xab\x0a\xb2\x5d\x5b\xbc\x62" + \
"\x60\x69\x84\xca\x60\xcc\xc3\x0e\x3f\xf8\x2e\x19\x0d\xc5\x02\xe9" + \
"\x88\x78\x03\x57\x3c\x3f\xcd\xd5\x98\xa7\xed\x15\x1e\x8c\xaf\x56" + \
"\x40\x0b\x75\xe8\x53\x53\x7b\x37\x81\x69\x5d\x78\x98\x11\xd8\xc1" + \
"\x9f\x1d\x10\x2d\x58\x73\x4f\xf2\xb5\xc2\x41\x29\x36\xb9\x1d\xa9" + \
"\xaa\x5c\x72\x70\x95\x07\x8d\xec\xc5\xb9\x41\x0d\xbb\xeb\x15\xc1" + \
"\x70\xb0\x4d\xa3\xeb\x09\xae\x05\x6c\x9b\xf5\x98\x04\x51\x44\x4f" + \
"\x5c\x2c\xac\xc9\x45\x9b\x87\xbd\x3d\x93\xf7\x03\x21\x80\xab\xb5" + \
"\xea\x95\x8d\xe5\x23\xb5\x16\xe3\x02\x65\x9a\x78\x3e\x74\x3b\x6d" + \
"\xe0\x98\xa3\xed\xc7\x03\x06\xc1\x32\x0b\x75\x10\x79\x67\x1c\x72" + \
"\x13\x12\xcb\x7c\xdc\x7c\x4d\x7b\x2c\x52\xaa\xc0\x90\xd1\x3a\xd7" + \
"\x1b\x63\xba\x18\x0c\x98\x3a\x9b\x37\xe7\xe6\x07\x08\x09\x51\xf7" + \
"\x44\xc3\xdd\x66\x76\x47\xa5\x9e\x5b\xee\x38\xad\x20\x0d\xf4\xc1" + \
"\x5d\xf2\x8d\x6a\x8b\x2b\x70\x3c\x1c\xce\x4d\x2a\xbe\x10\x22\x93" + \
"\x13\x37\x13\x1c\x96\x6e\xa5\x44\xe9\x19\xa9\x34\x8d\x93\x3e\xc7" + \
"\xd3\x15\x4b\xdb\x0e\x23\x20\xd4\x26\xc7\xfd\x60\x2d\x56\xb8\xa4" + \
"\xaf\x00\x0c\x5b\x73\x94\x17\x70\x33\xfe\x69\xce\xd4\x24\x61\x6f" + \
"\x02\xbc\xa7\x70\x71\x91\x62\x88\x32\x1c\x66\xff\x4d\xd8\x9f\xfc" + \
"\xf9\xf4\x63\x57\x59\x37\xb4\xfb\x2b\x80\xf9\x9a\x8b\x24\x83\x20" + \
"\x89\x78\x4c\xa7\xd6\xfc\xa8\x4a\xbc\x21\xa5\x8a\x84\xdc\xdc\xce" + \
"\x4c\x7e\xea\x44\xdc\x3d\xcf\x63\x7b\xe8\xef\x3f\xb8\xb4\x16\x0a" + \
"\xe5\x19\x4f\x39\x49\x91\x16\xfc\x0a\x9e\x0c\x8f\xf3\x2a\x73\x46" + \
"\xff\x9c\x82\x4d\x8c\x24\xd6\xa1\x93\xca\x83\xee\x51\xf7\xdc\x01" + \
"\xe1\x8b\x59\x33\xae\x4d\xfe\x15\xca\xf9\x99\x90\xfd\xaf\x56\x8b"
 
cipher = DES3.new(key, DES3.MODE_CBC,iv)
d = cipher.decrypt(msg)
 
print '\n'.join(textwrap.wrap(''.join(textwrap.wrap(d.encode('hex'),2)),64))    

Résultat

308201e6020100300d06092a864886f70d0101010500048201d0308201cc0201
00026100e11052645abad97049b654b4ad957da431ef10d89b9c205eb5c19d1e
18d352ef8bf1ab067bfd2f7b923e953a00d81a70584813af4d1abbf3b4988863
4d52a57c50ed17ef7effb8e6d5a3776c268c35a698d04248fe8c50ea35b8f8a8
ba93496d
0203010001026100982904cb3c5c1f2dbf683a147d57183eae53c6bb
eb58beb9c98040cd5c53633af90e466677e550f8e682d13909e110dda6a2baf1
9ce170d2ee344eadef89e6d34e047fa5317accf15ed81136fd89bee299792473
b2fad70a6a12077466330281
023100fbb93cdb6ef0c15cea749c27744beac5d9
d13b9cfabaa22dc950faa071b16384cb343c98020a67c4b4e156ea870e4c89
02
31
00e4e32387e734b14768bb68f35be78fe780784b79ea12dacb8fad32978775
ea7d2e89ccb7ceafd7f7c2a2f9eb9ace44c5
023100efc545039b4bf65323fc49
1587c01c73b3038aedff21b1033de445a67845488f1fe3b4e7f1e1a97003ff34
84167af749
023100e4a48daac5e89a2b0651281eea30e04cd6c5d447bdef05c7
9107e7e0fcbd6af78fd4b1beef66332c59af2a4f13fe772d
023002a7abe0f3d7
8a4b8183a7135fa1c22a8795898fad22c4cc514ce4bfb1ba6bb76152453771cc
4f53b2d49d3db8c89233
060606060606


en bleu : m (modulus de 00 E1 à 49 6D)
en bleu : e (exposant public 0x10001 = 65537d)
en vert : d (exposant privé de 00 98 à 02 81)
en vert : p (de 00 FB à 4C 89)
en vert : q (de 00 E4 à 44 C5)
en gris : d mod (p-1) (de 00 EF à F7 49)
en gris : d mod (q-1) (de 00 E4 à 772D)
en gris : q^-1 mod p => modular
en gris : inverse de q (de 02 A7 à 92 33)


ssh-passphrase-asn1-02

CONCLUSIONS

Nous venons de voir les 2 méthodes de chiffrement de clefs privées ssh
La première méthode qui est systématiquement utilisée ne résistera pas longtemps à une attaque par brute force ou à une attaque par dictionnaire si la passphrase est triviale.

La deuxième méthode est rarement utilisée en raison de son côté peu pratique. Il faut passer par une opération de conversion. Toutefois elle ralentira énormément une attaque par brute force ou par dictionnaire. Pour autant la passphrase ne doit pas être triviale.

 Publié par à 12 h 42 min
Août 262014
 

Le but de cet article est de présenter en détail les mécanismes cryptographiques mis en œuvre par DKIM (DomainKeys Identified Mail).

Référence : rfc4871

Présentation

DKIM (DomainKeys Identified Mail) est une norme d’authentification fiable du nom de domaine de l’expéditeur d’un courrier électronique. Elle constitue une protection efficace contre le spam et l’hameçonnage. En effet, DKIM fonctionne par signature cryptographique du corps du message et d’une partie de ses en-têtes. Une signature DKIM vérifie donc l’authenticité du domaine expéditeur et garantit l’intégrité du message.

Principes généraux

Génération de la clef privée et publique (RSA).

Dépot de la clef privée sur le MTA (serveur de mail) émetteur.
Dépot de la clef publique dans la zone DNS du domaine de l’expéditeur du mail.

  • Elle est déclarée sous la forme d’un enregistrement de type TXT.

Le MTA émetteur signe des éléments d’entêtes et le corps du message avec la clef privée et insère la signature dans l’entête du message.

La signature consiste :

  • à concaténer des éléments d’entêtes et le corps du message
  • à calculer le hash
  • et à chiffer le hash avec la clef privée

Le MTA destinataire, à la réception du mail, interroge l’enregistrement TXT de la zone DNS du domaine de l’expéditeur pour obtenir la clef publique.

  • Il déchiffre la signature avec la clef publique pour obtenir le hash.
  • Il concatène des éléments d’entêtes et le corps du message et en calcule le hash
  • Il compare les deux hashs :
    • S’ils sont identiques, cela valide l’authenticité du domaine expéditeur et garantit l’intégrité du message.

Algorithme en détail

Génération des clefs RSA

Le domaine est : cybercod.com

Les clefs peuvent être générées avec la commande suivante

opendkim-genkey -a -b 1024 -h sha256 -D /etc/opendkim/cybercod.com -d cybercod.com -s mail

-a : ajout de cybercod.com aprés mail._domainkey
-b : taille de la clef
-h : type de hashage
-D : dossier où seront générées les clefs
-d : nom du domaine
-s : sélecteur

La clef privée est générée dans /etc/opendkim/cybercod.com/mail.private
extrait :

-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDGQHEC1dOS9ORvySp992sLpFx0rEkgz/XrQlpZvdo69Gqz7Qq5
... ...
GqPNt5YkvhwVMbvSISiCd6Jszj5d2R8EUk77MZWZFH1y
-----END RSA PRIVATE KEY-----

la clef publique est générée dans /etc/opendkim/cybercod.com/mail.txt


mail._domainkey.cybercod.com. IN TXT "v=DKIM1; h=sha256; k=rsa; p=MIGfMA0G.......cQIDAQAB"

Le champ p contient la clef publique encodée en base64

On peut également générer les clef avec openssl.

Clef privée :

openssl genrsa -out mail.private 1024

Clef publique:

openssl rsa -in mail.private -out mail.txt -pubout -outform PEM

Affichage détaillé de la clef privée

openssl.exe rsa -noout -text -in <chemin de mail.private>

extrait :

Private-Key: (1024 bit)
modulus:
00:c6:40:71:02:d5:d3:92:f4:e4:6f:c9:2a:7d:f7:
6b:0b:a4:5c:74:ac:49:20:cf:f5:eb:42:5a:59:bd:
da:3a:f4:6a:b3:ed:0a:b9:36:63:3e:aa:2d:70:4c:
83:c9:86:b9:14:ad:94:aa:ee:ce:85:47:51:fc:86:
2f:ab:f1:37:64:23:73:47:5f:ab:4d:92:ae:ab:ca:
28:b4:cf:0f:d3:b7:a7:b9:8d:dd:71:aa:bd:ba:fc:
88:f0:ca:e9:cf:2f:2f:2e:55:76:3a:99:2c:2c:eb:
07:80:7c:65:cf:2f:79:e8:dc:6e:78:a3:c6:6b:59:
2e:a4:d6:60:5f:47:38:43:71
publicExponent: 65537 (0x10001)
privateExponent:
00:96:9f:5f:3d:48:37:f6:ef:18:9f:d5:b6:f2:fd:
87:d6:d0:89:6e:1b:77:73:f6:8c:60:b1:88:f3:a5:
ca:a8:00:0b:11:a8:86:fd:30:d5:36:47:15:3e:bc:
e3:63:b9:77:e4:bd:fc:b5:e1:ba:06:88:a9:41:b2:
b1:85:71:3f:22:ff:24:21:78:08:bc:22:43:ec:87:
e3:7a:1c:67:62:ab:56:0b:89:60:cf:5e:59:f6:79:
c0:f3:18:06:51:00:bf:d8:66:39:79:68:7c:2b:8a:
2d:61:14:b5:29:3c:0a:3c:79:ed:4b:87:48:c1:a6:
03:10:53:94:88:9b:a3:58:01

La commande suivante affiche également le détail de la clef privée.

openssl asn1parse -in <chemin de mail.private>

0:d=0 hl=4 l= 605 cons: SEQUENCE
4:d=1 hl=2 l= 1 prim: INTEGER :00
7:d=1 hl=3 l= 129 prim: INTEGER :C6407102D5D392F4E46FC92A7DF76B0B
A45C74AC4920CFF5EB425A59BDDA3AF46AB3ED0AB936633EAA2D704C83C986B914AD94AAEECE8547
51FC862FABF137642373475FAB4D92AEABCA28B4CF0FD3B7A7B98DDD71AABDBAFC88F0CAE9CF2F2F
2E55763A992C2CEB07807C65CF2F79E8DC6E78A3C66B592EA4D6605F47384371
139:d=1 hl=2 l= 3 prim: INTEGER :010001
144:d=1 hl=3 l= 129 prim: INTEGER :969F5F3D4837F6EF189FD5B6F2FD87D6
D0896E1B7773F68C60B188F3A5CAA8000B11A886FD30D53647153EBCE363B977E4BDFCB5E1BA0688
A941B2B185713F22FF24217808BC2243EC87E37A1C6762AB560B8960CF5E59F679C0F318065100BF
D8663979687C2B8A2D6114B5293C0A3C79ED4B8748C1A603105394889BA35801

Le modulus et l’exposant public seront utilisés dans les programmes python ci-dessous pour déchiffrer la signature.

Mécanismes cryptographiques

Prenons comme exemple le message suivant :

J’ai colorisé les champs qui vont être utiles.


# Début du message
From – Fri Aug 22 13:36:26 2014
X-Account-Key: account24
X-UIDL: 0000008153eb5b16
X-Mozilla-Status: 0001
X-Mozilla-Status2: 00000000
X-Mozilla-Keys:
Return-Path: <philippe@cybercod.com>
Delivered-To: <philippe@cybercod.com>
Received: from mail.cybercod.com
by ct-106.debian.local (Dovecot) with LMTP id tJCjFzIr91MeFgAARLzBNg
for <philippe@cybercod.com>; Fri, 22 Aug 2014 13:36:18 +0200
Received: from [192.168.0.9] (poo40-1-78-231-184-55.fbx.proxad.net [78.231.184.55])
by mail.cybercod.com (Postfix) with ESMTPSA id 39FC83826043
for <philippe@cybercod.com>; Fri, 22 Aug 2014 13:36:18 +0200 (CEST)
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=cybercod.com; s=mail;
t=1408707378; bh=g3zLYH4xKxcPrHOD18z9YfpQcnk/GaJedfustWU5uGs=;
h=Date:From:To:Subject:From;
b=P3zAiFT2nqXinY15ieBkFCNUgzEM0YtchzbCFFxJ4hIKYaKJg9GzFtdvg4Y2bbD2I
3BILrBtXRuodCICl+PLp4ssqA9uzqHo0qjHGpe+RZ0FwsS+ddA0FZ0xzRaZGoEvWu+
i58kfsYJy5pLQr/ygrGdzm464J4ODBkc/hb2NRr0=

Message-ID: <53F72B2E.2080609@cybercod.com>
Date: Fri, 22 Aug 2014 13:36:14 +0200
From: Philippe <philippe@cybercod.com>
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Thunderbird/31.0
MIME-Version: 1.0

To: philippe@cybercod.com
Subject: test

Content-Type: text/plain; charset=utf-8; format=flowed
Content-Transfer-Encoding: 7bit
0
test

# Fin du message

On extrait la signature DKIM.


DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=cybercod.com; s=mail;
t=1408707378;bh=g3zLYH4xKxcPrHOD18z9YfpQcnk/GaJedfustWU5uGs=;
h=Date:From:To:Subject:From;
b=P3zAiFT2nqXinY15ieBkFCNUgzEM0YtchzbCFFxJ4hIKYaKJg9GzFtdvg4Y2bbD2I3BILrBtXRuodCICl+PLp
4ssqA9uzqHo0qjHGpe+RZ0FwsS+ddA0FZ0xzRaZGoEvWu+i58kfsYJy5pLQr/ygrGdzm464J4ODBkc/hb2NRr0=

pour information, le champ « t » contient la date de création de la signature au format epoch unix (nombre de secondes depuis le 01 janvier 1970).
Pour la conversion aller sur le site suivant : epochconverter.com
Dans cette exemple la signature a été créée le : 22/8/2014 13:36:18 GMT+2

Deux champs nous intéressent plus particulièrement.

Le champ bh
Le champ b

Le champ bh contient le hash du corps du message. Le hash est encodé en base64
Vérifions :

Le texte du message est : test
Attention il faut ajouter la fin de ligne (0D et 0A)

Nota : toutes les lignes vides en fin de texte sont supprimées. C’est à dire qu’elles ne sont pas prises en compte pour le calcul du hash.

Dans un éditeur hexadécimal, taper la ligne suivante.

Offset(d) 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
00000000  74 65 73 74 0D 0A                                 test..

Sauvegarder le fichier en le nommant bh.txt

calculons le hash sha256 de bh.txt

Nous obtenons : 837ccb607e312b170fac7383d7ccfd61fa5072793f19a25e75fbacb56539b86b

bh

Attention, il s’ agit d’une string hexa , il faut la convertir en base64

programme python de conversion :

import base64

StringHexa="837ccb607e312b170fac7383d7ccfd61fa5072793f19a25e75fbacb56539b86b"
ChaineBase64guid=base64.encodestring(StringHexa.decode('hex'))
print ChaineBase64

On obtient : g3zLYH4xKxcPrHOD18z9YfpQcnk/GaJedfustWU5uGs=

Ce qui correspond bien au champ bh contenu dans la ligne dkim.

On constate donc que la ligne DKIM contient un champ nommé bh qui est tout simplement un hash sha256 (non chiffré) du corps du message

Étudions maintenant le champ b.

Principe de sa génération :

Le champ b va être constitué de la concaténation de certaines lignes du header et de la ligne dkim (avec le champ bh précédemment calculé et le champ b à vide ).

Ensuite on exécute un hash sha256 à partir des lignes concaténées
et on signe avec la clef privée.

Dans la ligne dkim , le champ "h" va nous préciser quelles sont les lignes de l’entête qui vont être concaténées pour calculer le hash sha256
Dans le message "h" est égal à :

h=Date:From:To:Subject:From;

Donc on va concaténer : « Date » || « From » || « To » || « Subject »

(attention : Le champ From n’existant qu’une seule fois dans le message, le deuxième From n’ est pas pris en compte pour le calcul du hash)

Cela donne donc :

Date: Fri, 22 Aug 2014 13:36:14 +0200
From: Philippe <philippe@cybercod.com>
To: philippe@cybercod.com
Subject: test
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=cybercod.com; s=mail;
t=1408707378; bh=g3zLYH4xKxcPrHOD18z9YfpQcnk/GaJedfustWU5uGs=;
h=Date:From:To:Subject:From;
b= <======= b est vide et pas de retour à la ligne

Normal que b soit vide puisque c’ est justement la valeur que l’on calcule

Affichage dans un éditeur hexadécimal :

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000 44 61 74 65 3A 20 46 72 69 2C 20 32 32 20 41 75 Date: Fri, 22 Au
00000010 67 20 32 30 31 34 20 31 33 3A 33 36 3A 31 34 20 g 2014 13:36:14
00000020 2B 30 32 30 30 0D 0A 46 72 6F 6D 3A 20 50 68 69 +0200..From: Phi
00000030 6C 69 70 70 65 20 3C 70 68 69 6C 69 70 70 65 40 lippe <philippe@
00000040 63 79 62 65 72 63 6F 64 2E 63 6F 6D 3E 0D 0A 54 cybercod.com>..T
00000050 6F 3A 20 70 68 69 6C 69 70 70 65 40 63 79 62 65 o: philippe@cybe
00000060 72 63 6F 64 2E 63 6F 6D 0D 0A 53 75 62 6A 65 63 rcod.com..Subjec
00000070 74 3A 20 74 65 73 74 0D 0A 44 4B 49 4D 2D 53 69 t: test..DKIM-Si
00000080 67 6E 61 74 75 72 65 3A 20 76 3D 31 3B 20 61 3D gnature: v=1; a=
00000090 72 73 61 2D 73 68 61 32 35 36 3B 20 63 3D 73 69 rsa-sha256; c=si
000000A0 6D 70 6C 65 2F 73 69 6D 70 6C 65 3B 20 64 3D 63 mple/simple; d=c
000000B0 79 62 65 72 63 6F 64 2E 63 6F 6D 3B 20 73 3D 6D ybercod.com; s=m
000000C0 61 69 6C 3B 0D 0A 09 74 3D 31 34 30 38 37 30 37 ail;...t=1408707
000000D0 33 37 38 3B 20 62 68 3D 67 33 7A 4C 59 48 34 78 378; bh=g3zLYH4x
000000E0 4B 78 63 50 72 48 4F 44 31 38 7A 39 59 66 70 51 KxcPrHOD18z9YfpQ
000000F0 63 6E 6B 2F 47 61 4A 65 64 66 75 73 74 57 55 35 cnk/GaJedfustWU5
00000100 75 47 73 3D 3B 0D 0A 09 68 3D 44 61 74 65 3A 46 uGs=;...h=Date:F
00000110 72 6F 6D 3A 54 6F 3A 53 75 62 6A 65 63 74 3A 46 rom:To:Subject:F
00000120 72 6F 6D 3B 0D 0A 09 62 3D                      rom;...b=

Sauvegarder le fichier en le nommant b.txt

Un sha256 des lignes ci-dessus donne
58632e538ba175cce1cdeda787a57ff16755e7026b1454dc3b4951072538dd08

b

Plutôt que de vouloir signer le hash obtenu, nous allons à l’ inverse partir de la signature
jusqu’à obtenir le hash.

La signature est encodée en base 64 , décodons la en hexstring.

P3zAiFT2nqXinY15ieBkFCN............r/ygrGdzm464J4ODBkc/hb2NRr0=

Programme en python conversion base64 vers hexstring.

import base64
 
ChaineBase64= \
"P3zAiFT2nqXinY15ieBkFCNUgzEM0YtchzbCFFxJ4hIKYaKJg9GzFtdvg4Y2bbD2I" + \
"3BILrBtXRuodCICl+PLp4ssqA9uzqHo0qjHGpe+RZ0FwsS+ddA0FZ0xzRaZGoEvWu" + \
"+i58kfsYJy5pLQr/ygrGdzm464J4ODBkc/hb2NRr0="
StringHexa=base64.decodestring(ChaineBase64).encode('hex')
print StringHexa

On obtient la chaine suivante. Je l’ai découpée en groupe de 32 octets sur 4 lignes.

Signature :
3f7cc08854f69ea5e29d8d7989e06414235483310cd18b5c8736c2145c49e212
0a61a28983d1b316d76f8386366db0f62370482eb06d5d1ba874220297e3cba7
8b2ca80f6ecea1e8d2a8c71a97be459d05c2c4be75d034159d31cd16991a812f
5aefa2e7c91fb18272e692d0affca0ac67739b8eb827838306473f85bd8d46bd

Déchiffrement de la signature :

Programme en python de déchiffrement de la signature

# n=modulus
n= int( \
"C6407102D5D392F4E46FC92A7DF76B0BA45C74AC4920CFF5EB425A59BDDA3AF4" + \
"6AB3ED0AB936633EAA2D704C83C986B914AD94AAEECE854751FC862FABF13764" + \
"2373475FAB4D92AEABCA28B4CF0FD3B7A7B98DDD71AABDBAFC88F0CAE9CF2F2F" + \
"2E55763A992C2CEB07807C65CF2F79E8DC6E78A3C66B592EA4D6605F47384371" \
,16)
# exposant public
e = 0x10001
# signature
signature= int( \
"3f7cc08854f69ea5e29d8d7989e06414235483310cd18b5c8736c2145c49e212" + \
"0a61a28983d1b316d76f8386366db0f62370482eb06d5d1ba874220297e3cba7" + \
"8b2ca80f6ecea1e8d2a8c71a97be459d05c2c4be75d034159d31cd16991a812f" + \
"5aefa2e7c91fb18272e692d0affca0ac67739b8eb827838306473f85bd8d46bd" \
,16)
# fonction pow : (signature ^ e) mod n
hash=pow(signature,e,n)
print hex(hash)

On obtient la chaine suivante

0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffff003031300d060960864801650304020105000420
58632e538ba175cce1cdeda787a57ff16755e7026b1454dc3b4951072538dd08

ajouter au début de la chaine un octets à 0 (0x00) pour être conforme à la rfc 3447.
Affichage sous forme hexa


00000000 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000 00 01 FF FF FF FF FF FF FF FF FF FF FF FF FF FF ..ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
00000010 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
00000020 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
00000030 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
00000040 FF FF FF FF FF FF FF FF FF FF FF FF 00 30 31 30 ÿÿÿÿÿÿÿÿÿÿÿÿ.010
00000050 0D 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 ...`†H.e........
00000060 0D 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 Xc.S‹¡uÌáÍí§‡¥.ñ
00000070 0D 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 gUç.k.TÜ;IQ.%8Ý.

La RFC 3447 chapitre 9.2 EMSA-PKCS1-v1_5 décompose la chaine selon le format suivant :

EM= 0x00 || 0x01 || PS ||0x00 || T

PS : (Padding String) composé d’une série d’octets à 0xff
T : restant de la chaine

Pour information, voici la formule à appliquer pour connaitre la longueur du padding.
Len(EM) – Len(T) – 3 = 128 – 51 – 3 = 74 (longueur du padding 0xFF)

EM : Nombre d’octets composant le modulus. La taille du modulus est de 1024 bits donc 128 octets.

Le nombre d’octets égal à 0xff sera 74.

Le reste de la chaine T est au format asn.1

On devine le hash à la fin de la a chaine T .
003031300d06096086480165030402010500042058632e538ba175cce1cdeda787a57ff16755e7026b1454dc3b4951072538dd08

Si on parse la chaine dans un éditeur asn.1 on obtient le hash
58632e538ba175cce1cdeda787a57ff16755e7026b1454dc3b4951072538dd08

dkim-asn.1

L’ OID 2.16.840.1.101.3.4.2.1 identifie un hash sha256.
Le hash correspond bien au hash que nous avons calculé en concaténant certaines lignes composant l’entête du message et la ligne dkim (avec "b=" à vide).

Voila , nous avons parcouru les mécanismes cryptographiques mis en œuvre par DKIM.

Annexe
canonicalization (forme canonique)

Définition :
En informatique , le passage à la forme canonique permet de transformer des données dont plusieurs représentations sont possibles vers un format « standard ».

Certains systèmes de mail modifient les mails en transit ce qui potentiellement invalidera la signature.

Le mode « simple » n’autorise qu’une seule modification pendant le transit du mail, l’ajout ou la suppression de fin de ligne dans le corps du message

Si d’autres modifications sont effectuées , la signature ne sera pas correcte

Le mode « relaxed » autorise quelques petits changements pendant le transit du mail.
A l’envoi , les données sont transformées vers un format standard (forme canonique) puis la signature est calculée
A la réception, les données sont de nouveau transformées vers le même format stantard (forme canonique) puis la vérification de la signature est executée

Explication concernant le champ « c » de la ligne dkim

c=([simple ou relaxed]/[relaxed ou simple])

le 1er champ concerne le header (dkim compris)
le 2ème champ concerne le corps du message

Exemple :

  • c=relaxed/simple

Dans cet exemple on utilise

  • relaxed pour le header
  • et simple pour le corps du message

Mode relaxed

entête (header)
Dans l’entête , chaque ligne peut se décomposer en 2 parties
un champ "nom" et un champ "donnée" séparés par ":"
exemple :
Date: Fri, 22 Aug 2014 13:36:14 +0200
Le champ « nom » est : Date
Le champ « donnée » est : Fri, 22 Aug 2014 13:36:14 +0200

Pour chaque champ « nom ».

  • Conversion en minuscules.

Pour chaque champ "donnée"
dans cet ordre

  • supprime tout les retours à la ligne (\r\n) dans le champ donnée
  • remplace tous les caractères [ \t\n\r\f\v] par un espace
    • \t => 0x09
    • \n => 0x0a
    • \r => 0x0d
    • \f => 0x0c
    • \v => 0x0b
  • supprime tous les espaces en début de ligne et en fin de ligne (0x20)
  • rajoute une fin de ligne (\r\n)

Attention le champ DKIM fait partie de l’entête

Expression régulière résultante en python :

  • headers contient l’entête (dictionnaire python).
  • return [(x[0].lower(), re.sub(r"\s+", " ", re.sub("\r\n", "", x[1])).strip()+"\r\n") for x in headers]

Corps (body)

  • Supprime les séries d’espaces (espace et tabulation) en fin de lignes
  • Remplace les séries d’espaces (espace et tabulation) par un seul espace dans une ligne à l’ exception des fins de lignes
  • Ignore toutes les lignes vides à la fin du corps du message.

Expression régulière résultante en python :

  • body : contient le corps du message (chaine de caractères)
  • return re.sub("(\r\n)*$", "\r\n", re.sub(r"[\x09\x20]+", " ", re.sub("[\x09\x20]+\r\n", "\r\n", body)))

Mode simple

Entête (header) du message

  • Pas de changement au niveau du header.

Corps (body) du message

  • Ignore toutes les lignes vides à la fin du corps du message.

Expression régulière résultante en python

  • body : contient le corps du message (chaine de caractères)
  • return re.sub("(\r\n)*$", "\r\n", body)
 Publié par à 16 h 31 min
Mai 252014
 

Cet article présente les algorithmes crypto HOTP et TOTP.

Exemples de support matériel ou logiciel utilisant les protocoles HOTP et TOTP.

YubiKey-SmartPhone

Associés à un code pin, on obtiendra une authentification forte.

Définition authentification forte :

  • L’utilisation de 2 facteurs d’authentification parmi les 3 cités ci-dessous.
    • Ce que je sais.
      • Un mot de passe, un code pin
    • Ce que je possède.
      • Un support matériel. Token,smartphone,smartcard …
    • Ce que je suis.
      • Une empreinte biométrique

OTP
One Time Password

One Time Pasword : Mot de passe à usage unique.

2 variantes d’otp :

Le totp :

  • Le Time-based One Time Password (Time Based Tokens)
  • Basé sur le temps

Le hotp :

  • Le Hmac-based One Time Password (Event Based Tokens)
  • Basé sur un compteur

Algorithme TOTP et HOTP

Les algorithmes sont identiques. Seul le type de compteur change. TOTP est basé sur HOTP :

Pour hotp :

  • Le compteur est un compteur de type incrémentiel
    0, 1 , 2 , 3 etc ...

Pour totp

  • Le compteur est un compteur basé sur le temps (epoch Posix)
  • Pour mesurer le temps, il faut choisir une origine :
    • L’origine de l’heure POSIX a été choisie comme étant le 1er janvier 1970 00:00:00 UTC
    • Cette date correspond à l’heure 0 de Posix
    • Elle est appelée l’epoch Posix (et également epoch Unix).

Exemple en python

import time
int(time.time())

Retourne le nombre de secondes depuis le 1er janvier 1970 00:00:00 UTC

Algorithme TOTP

K est la clef secrète (clef partagée)
TC est le compteur de temps

Etape 1 :

Génération d’un HMAC-SHA1

HMAC(K,TC) = SHA1( (K  xor 0x5c5c…)  || SHA1((K  xor 0x3636… || TC))

Retourne 20 octets (longueur du HMAC-SHA1)

Etape 2 :

Extraction d’une chaine de 31 bits à partir du HMAC calculé à l’ étape 1

Sbits = DT(HMAC(K,TC))

La fonction DT (Dynamic Truncation) est détaillée plus bas.
Retourne une chaine de 31 bits

Etape 3 :

Génère un token à partir de la chaine Sbits calculée à l’ étape 2

Snum  = StToNum(Sbits)

StToNum convertit Sbits en décimal

T = Snum mod 10^Digit
  • Digit : longueur du token : généralement 6
  • mod : modulo
  • T : Retourne une valeur comprise entre 0 et 999999 qui correspond au token

Description de la fonction DT (Dynamic Truncation) de l’étape 2

DT(String)

  • String, qui est le hmac calculé à l’étape 1, est une chaine composée de 20 octets (String[0]…String[19])
  • On extrait les 4 bits de poids faible de l’ octet 19. La valeur est contenue dans OffsetBits
    • OffsetBits est donc compris entre 0 et 15
    • OffsetBits donne la position dans String pour extraire les 4 octets

affichage hexa :

0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5  6  7  8  9   
xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xy
                                                          ^

Les 4 derniers bits (y) donnent la position où sont extraits les 4 octets.

  • Extraction :
    • Offset = StToNum(OffsetBits)
    • Extraction de 4 octets à partir de OffsetBits
    • Retourne 31 bits de Offset (le bit de signe est positionné à 0)
    • Exemple :
      • si OffsetBits est égal à 5 (0101 en binaire), on extrait aa bb cc dd à partir de l’ octet 5

Affichage hexa :

0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5  6  7  8  9   
xx xx xx xx xx aa bb cc dd xx xx xx xx xx xx xx xx xx xx xy
               ^^ ^^ ^^ ^^                                ^

Puis on positionne à 0 le bit le plus significatif

    a    a    b    b    c    c    d    d
    xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 
    0xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
    ^  

La raison de masquer le bit le plus significatif (bit de signe) est d’éviter des erreurs de calcul (bit de signe).

Pour lever toute ambiguïté l’algorithme supprime le bit de signe.

Exemple TOTP

Préparation des données :

K est le secret partagé :

  • en base 32: LVACVMXXD4AIPVLFZ6QL337GS2MM7X5Y
    • la clef est généralement en base 32 pour simplifier et éviter les erreurs de saisies
  • Conversion en hexa : 5d402ab2f71f0087d565cfa0bdefe69698cfdfb8
    • (convertisseur on line base32 -> base 16 : tomeko.net)

C (compteur) est le compteur de temps :

  • 1400320015 = Samedi 17 mai 2014 / 09h 46m 55s (Conversion on line www.epochconverter.com)
  • TC = (unixtime(now) – unixtime(T0)) / TS (1400320015 /30) = 46677333
  • TS est généralement égale à 30 – cela permet de découper le temps en tranche de 30 secondes
  • Conversion en hexa
    • 46677333d = 0x2C83D55
  • Conversion en 8 octets
    • 0000000002C83D55

Les données sont préparées il faut maintenant appliquer les 3 étapes de l’algorithme :

Etape 1 : HMAC-SHA1
HMAC(K,TC) = SHA1((K xor 0x5c5c…) || SHA1((K xor 0x3636… || TC))
HMAC(5d402ab2f71f0087d565cfa0bdefe69698cfdfb8, 0000000002C83D55)

On obtient : e562f2dadefc81a384551c3278d5ec42417ab4da

Vérification avec hashcalc

hotp-hashcalc

Etape 2 : Extraction des 4 octets et potitionnement à 0 du bit de signe

Extraction des 4 octets:

offset 0  1  2  3  4  5  6  7  8  9  0  1  2  3  7  5  6  7  8  9
       e5 62 f2 da de fc 81 a3 84 55 1c 32 78 d5 ec 42 41 7a b4 da
                                     ^  ^  ^  ^                  ^

les 4 derniers bits donnent l’ offset : 0xa (10d => d comme décimal)
Les 4 octets à extraire sont donc : 1c 32 78 d5

Positionnement à 0 du 31 ème bit
1c 32 78 d5 on ne conserve que les 31 bits (le dernier bit à gauche est positionné à 0)
1c 32 78 d5 and 7f ff ff ff = 1c 32 78 d5
Dans cet exemple, le bit de signe étant déjà égal à 0 , cela ne change rien

Etape 3 : Génération du token
  • Conversion en décimal
    • 1c3278d5 = 473069781
  • Modulo 10^6
    • 473069781 % 10^6 = 069781

Nous obtenons le token : 069781

Exemple HOTP

On remplace simplement le compteur de temps par un compteur incrémentiel

00 00 00 00 00 00 00 01
00 00 00 00 00 00 00 02
00 00 00 00 00 00 00 03 etc ...
Préparation des données :

K est le secret partagé :

  • en base 32: WLBQPZMXG5EML4IOPZPAW5K6HRWAW5DQ
    la clef est généralement en base 32 pour simplifier et éviter les erreurs de saisies
  • Conversion en hexa : B2C307E5973748C5F10E7E5E0B755E3C6C0B7470
    (convertisseur on line base32 -> base 16 : tomeko.net)

C (compteur) est le compteur incrémentiel

  • Dans cet exemple le compteur est égal à 8 (conversion sur 8 octets)
  • On obtient : 0000000000000008
Etape 1 : HMAC-SHA1
 HMAC(K,TC) = SHA1((K xor 0x5c5c…) || SHA1((K xor 0x3636… || TC))
 HMAC(B2C307E5973748C5F10E7E5E0B755E3C6C0B7470, 0000000000000008)
 On obtient : aeeed68be51b23e1b7ea1a1aaae93bdee303b439

totp-hashcalc

Etape 2 : extraction des 4 octets et positionnement à 0 du bit de signe

Extraction des 4 octets:

     offset 0  1  2  3  4  5  6  7  8  9  0  1  2  3  7  5  6  7  8  9
            ae ee d6 8b e5 1b 23 e1 b7 ea 1a 1a aa e9 3b de e3 03 b4 39
                                       ^^ ^^ ^^ ^^                    ^

les 4 derniers bits donnent l' offset : 0x09 (9d => d comme décimal)
Les 4 octets à extraire sont donc : ea 1a 1a aa

Positionnement à 0 du 31ème bit
ea 1a 1a aa on ne conserve que les 31 bits (le dernier bits à gauche est positionné à 0)
ea 1a 1a aa and 7f ff ff ff = 6a 1a 1a aa

Etape 3 : Génération du token
  • Conversion en décimal
    • 6a1a1aaa = 1780095658
  • Modulo 10^6
    • 1780095658 % 10^6 = 095658

Nous obtenons le token : 095658

Programme en python TOTP

import sys
import base64, hashlib, hmac
import struct, time, math
 
def get_totp_token(secret, time_window, key_len):
    key = base64.b32decode(secret)
    msg = struct.pack(">Q", time_window)
    h = hmac.new(key, msg, hashlib.sha1).digest()
    o = ord(h[19]) & 0x0f
    otp = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff)
    return '%6d' % (otp % int(math.pow(10, key_len)))
 
def test_user_pin(secret, key_len, user_pin):
    time_window = int(time.time())/30
    print "Reference temps " + str(time_window)
    for offset in [0, -1, -2, 1]:
        print "offset:  " + str(offset * 30)
        cur_pin = get_totp_token(secret,time_window+offset, key_len)
        if str(cur_pin) == str(user_pin):
            print '   temps: '+str(time_window+offset), 'token: '+ str(cur_pin)
            return True
    return False
 
# Déclarer le secret partagé codé en 32 bits dans la variable : secret
secret = "LVACVMXXD4AIPVLFZ6QL337GS2MM7X5Y"
key_len = 6
 
# Déclarer le token dans la variable : user_token
user_token = "364525"
if test_user_pin(secret, key_len, user_token):
    print " ACCEPT"
else:
    print " DENY"

Programme en python HOTP

import hmac, base64, struct, hashlib, time
 
def get_hotp_token(secret, intervals_no):
    key = base64.b32decode(secret, True)
    msg = struct.pack(">Q", intervals_no)
    h = hmac.new(key, msg, hashlib.sha1).digest()
    o = ord(h[19]) & 0x0f
    h = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000
    return h
 
 
secret = 'WLBQPZMXG5EML4IOPZPAW5K6HRWAW5DQ'
print 'Count', 'Token'
for i in xrange(8, 16):
    print '%04d' %i, ' %06d' % get_hotp_token(secret, intervals_no=i)

Affiche les 7 premiers token.

Comme vous pouvez le voir, l’algorithme de génération de token n’ est pas bien compliqué.
Pour des explications complémentaires je vous renvoie vers la RFC 4226.

 Publié par à 21 h 29 min
Mar 162014
 

Extrait configuration ASA


ASA Version 8.4(2)
!
hostname ciscoasa
enable password 8Ry2YjIyt7RRXU24 encrypted
passwd 2KFQnbNIdI.2KYOU encrypted

Le mot de passe par défaut pour accéder à l’ ASA est « cisco ».

  • le hash est 2KFQnbNIdI.2KYOU

Le mot de passe pour passer en mode enable est vide , le hash est : 8Ry2YjIyt7RRXU24.

On retrouve très souvent ces hashs dans les configurations. Cela signifie que l’ administrateur n’a tout simplement pas changé les mots de passe par défaut . . .

Algorithme de calcul du hash

Ajout de padding ‘\0’ au mot de passe en clair pour obtenir une taille de 16 octets
Calcul du hash md5
Conversion en base 64 du hash md5 (façon freebsd – _crypt_to64)

Padding et hash md5

Avant de calculer le hash md5 du mot de passe , il faut compléter le mot de passe avec la valeur
de remplissage « \0 » (Null) pour obtenir une longueur de 16 octets

exemple pour le mdp : cisco

Dans un éditeur hexadécimal saisissez la ligne suivante

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  63 69 73 63 6F 00 00 00 00 00 00 00 00 00 00 00 cisco...........
                         ^  ^  ^  ^  ^  ^  ^  ^  ^  ^  ^

11 valeurs ‘\00’ sont ajoutées

Sauvegarder :

Calculer le md5 , on obtient : 841571baf39951012905107416a981c7

Conversion Base 64 – _crypt_to64

Table de conversion base64.

                   1         2         3         4         5         6
         0123456789012345678901234567890123456789012345678901234567890123
ITOA64 = ./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

Découpage du hash en groupe de 4 octets :

841571ba f3995101 29051074 16a981c7

Le premier octet de poids fort de chaque groupe n’ est pas utilisé.

1er groupe :

7    1    1    5    8    4
0111 0001 0001 0101 1000 0100
^      ^       ^      ^

011100 010001 010110 000100  (on créé 4 groupes de 6 bits)   
28d    17d    22d    04d     (d : décimal)
"Q"    "F"    "K"    "2"     (utilisation de la table de conversion base 64)

Le résultat se lit de la droite vers la gauche. On obtient 2KFQ

2ème groupe :

5    1    9    9    f    3
0101 0001 1001 1001 1111 0011
^      ^       ^      ^ 

010100 011001  100111 110011  (on crée 4 groupes de 6 bits)  
20d    25d     39d    51d     (d : décimal)
"I"    "N"     "b"    "n"     (utilisation de la table de conversion base 64)

Le résultat se lit de la droite vers la gauche. On obtient nbNI

3éme groupe

1    0    0    5    2    9
0001 0000 0000 0101 0010 1001
^      ^       ^      ^

000100 000000  010100 101001  (on crée 4 groupes de 6 bits)
04d    00d     20d    41d     (d : décimal)
"2"    "."     "I"    "d"     (utilisation de la table de conversion base 64)

Le résultat se lit de la droite vers la gauche. On obtient dI.2

4ieme groupe

8    1    a    9    1    6
1000 0001 1010 1001 0001 0110
^      ^       ^      ^  

100000 011010  100100 010110  (on crée 4 groupes de 6 bits)
32d    26d     36d    22d     (d : décimale)
"U"    "O"     "Y"    "K"     (utilisation de la table de conversion base 64)

Le résultat se lit de la droite vers la gauche. On obtient KYOU

Concaténons les 4 chaines obtenues

2KFQ || nbNI || dI.2 || KYOU = 2KFQnbNIdI.2KYOU

Programme python

Pour sélectionner tout le code, double-cliquez dans la fenêtre.

# -*- coding: cp1252 -*-
 
#                   1         2         3         4         5         6
#         0123456789012345678901234567890123456789012345678901234567890123
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 
import hashlib
import struct
 
def to64 (v, n):
   ret = ''
   while (n - 1 >= 0):
      n = n - 1
      ret = ret + ITOA64[v & 0x3f]
      v = v >> 6
   return ret
 
def md5_asa(pw):
 
   # padding du mot de passe
   data = ['\x00'] * 16
   pos=0
   for car in pw:
       data.insert(pos,car)
       pos=pos+1
   # transforme la liste en buffer
   password = struct.pack('c'*len(data), *data)
 
   # hash du mot de passe
   # cisco  : 841571baf39951012905107416a981c7
   # (vide) : 4ae71336e44bf9bf79d2752e234818a5
   hash=hashlib.md5()
   hash.update(password[0:16])
   print "hash md5 : " + hash.hexdigest()
   hashdigest = hash.digest()
 
   output=''
 
   output=output + to64((int(ord(hashdigest[2]))) << 16 | (int(ord(hashdigest[1]))) << 8 | (int(ord(hashdigest[0]))),4)
   output=output + to64((int(ord(hashdigest[6]))) << 16 | (int(ord(hashdigest[5]))) << 8 | (int(ord(hashdigest[4]))),4)
   output=output + to64((int(ord(hashdigest[10]))) << 16 | (int(ord(hashdigest[9]))) << 8 | (int(ord(hashdigest[8]))),4)
   output=output + to64((int(ord(hashdigest[14]))) << 16 | (int(ord(hashdigest[13]))) << 8 | (int(ord(hashdigest[12]))),4)
 
   return  output
 
if __name__ == "__main__":
 
   # mot de passe :  cisco  -  hash : 2KFQnbNIdI.2KYOU
   password="cisco"
   # arg01: password
   print "password : " + password + "  - hash asa : " + md5_asa(password)
   # mot de passe : cisco - hash : 8Ry2YjIyt7RRXU24
   password=""
   print "password : (vide) " + "- hash asa : " + md5_asa(password)    

Cain a implémenté l’algorithme,on peut l’utiliser pour réaliser un brute force

www.oxid.it

Référence : http://www.securiteam.com/securitynews/5FP0P0A7FM.html

 Publié par à 23 h 57 min
Fév 142014
 

CISCO secret 4 vulnérabilité

La vulnérabilité est confirmée officiellement dans le bulletin cisco id: 33464 du 18 mars 2013.

Certaines versions de Cisco IOS XE and Cisco IOS Software Release 15 intègrent un nouvel algorithme de hashage appelé secret 4. Les spécifications devaient être les suivantes :

algorithme de hashage : sha 256
un salt de 80 bits
1000 itérations

Tout naturellement il était destiné à remplacer le secret 5 qui lui est basé sur md5crypt.

Mais malheureusement, à cause d’un problème de mise en œuvre, le secret 4 s’est étrangement transformé
en un simple sha256 (pas de sel , pas d’itération). Sont impactés tous les CISCO IOS and IOS XE supportant le secret 4.

Du coup , CISCO préconise de ne pas utiliser le hash secret 4 , mais d’ utiliser le hash secret 5.

Mais là on rencontre une difficulté. En effet, lorsque l’on déclare un utilisateur sur un équipement CISCO, on saisit la commande suivante:
username secret

Mais cette commande ne permet pas de choisir entre le secret 4 et le secret 5.
Le secret 4 est choisi automatiquement.

2 méthodes de contournement:

1/
Pour déclarer un hash secret 5, il faut le générer avec une application tierce (exemple avec openssl)
et l’importer en le copiant dans la config du routeur

Commande openssl :

openssl passwd -1 -salt

Exemple :

salt = 12345678
Mot de passe : hashcat

openssl passwd -1 -salt 12345678 hashcat

Nous obtenons le hash md5crypt : $1$12345678$oBguOQT6/v2L/9ZuzX4Cq0

Commande pour déclarer un utilisateur avec un hash secret 5 sur un équipement CISCO

username secret 5 $1$12345678$oBguOQT6/v2L/9ZuzX4Cq0

2/
Générer un hash secret 5 sur un IOS ne supportant pas encore le secret 4 et ensuite copier-coller.

Pour finir, j’ aimerais bien que l’on m’explique comment une société comme CISCO a pu faire une telle erreur d’intégration ?
N’ hésiter pas à me laisser un commentaire.

 Publié par à 12 h 26 min
Jan 012014
 

Certificat X509 et signature

Le but de cet article est de présenter en détail les mécanismes cryptographiques mis en œuvre pour signer un certificat x509.

Remerciement à Gentilkiwi pour sa contribution.

Qu’est ce que la signature d’un certificat ?

Cela consiste à:
– calculer le hash du certificat
– signer le hash avec la clef privée de l’autorité de certification
(si le certificat est auto-signé on utilise la clef privée du certificat pour signer le certificat).

Dans cet exemple, je vais utiliser les algorithmes suivants pour signer le certificat :
MD5 : pour le calcul du hash.
RSA 384 bits : pour la signature.

Le certificat est auto-signé.

je commence par générer une clef privée (privkey.pem) et un certificat autosigné (cert.pem).

openssl req -x509 -config ../share/openssl.cnf -newkey rsa:384 -nodes -md5 -subj « /C=FR/L=LOCALITE/CN=blog.cybercod.com » -out cert.pem

Affichons le certificat autosigné obtenu avec cette commande.

openssl x509 -in cert.pem -text -noout


Certificate:
000Data:
00000Version: 3 (0x2)
0000Serial Number:
0000000b9:53:e7:51:87:18:5b:05
0000Signature Algorithm: md5WithRSAEncryption
0000Issuer: C=FR, L=LOCALITE, CN=blog.cybercod.com
00000Validity
000000Not Before: Dec 30 12:27:28 2013 GMT
000000Not After : Jan 29 12:27:28 2014 GMT
00000Subject: C=FR, L=LOCALITE, CN=blog.cybercod.com
00000Subject Public Key Info:
000000Public Key Algorithm: rsaEncryption
000000RSA Public Key: (384 bit)
000000000Modulus (384 bit):
0000000000000:bf:37:81:4a:5a:a9:01:ff:b3:c7:0c:f8:31:66:
00000000000bd:a4:d6:b3:ae:01:b7:f9:af:ea:08:71:87:d0:12:
00000000000a4:d3:d4:46:07:62:79:bb:88:d6:e4:96:bd:2c:fa:
0000000000046:e3:7b:8f
000000000Exponent: 65537 (0x10001)
0000X509v3 extensions:
000000X509v3 Subject Key Identifier:
00000000003:31:E9:83:37:81:D7:C7:3E:81:77:32:6C:DD:33:73:A3:20:AC:E2
000000X509v3 Authority Key Identifier:
000000000keyid:03:31:E9:83:37:81:D7:C7:3E:81:77:32:6C:DD:33:73:A3:20:AC:E2
000000000DirName:/C=FR/L=LOCALITE/CN=blog.cybercod.com
000000000serial:B9:53:E7:51:87:18:5B:05
000000X509v3 Basic Constraints:
000000000CA:TRUE
0Signature Algorithm: md5WithRSAEncryption
00003f:ee:39:86:ec:f4:c8:c0:b8:2c:2a:15:70:54:c9:69:f4:38:
000043:ec:49:c8:74:ba:8d:df:8e:1c:6a:89:61:ef:c7:60:4b:15:
0000d9:1c:37:e8:38:a6:41:fd:47:02:d4:6d

en bleu :
La clef publique du certificat qui est composée du modulus et de l’exposant public.

en rouge :
La signature.

Déchiffrement de la signature.

Le hash du certificat a été chiffré en RSA avec la clef privée du certificat (modulus et exposant privé).
Pour obtenir le hash, il suffit de déchiffrer en RSA avec la clef publique du certificat (modulus et exposant public).
Le modulus et l’exposant public se trouvent dans le certificat.

On supprime les « : » du modulus et on concatène les octets.

00bf37814a5aa901ffb3c70cf83166bda4d6b3ae01b7f9afea087187d012a4d3d446076279bb88d6e496bd2cfa46e37b8f 

L’exposant public :
10001 (valeur hexa)

On supprime les « : » de la signature et on concatène les octets.

3fee3986ecf4c8c0b82c2a157054c969f43843ec49c874ba8ddf8e1c6a8961efc7604b15 d91c37e838a641fd4702d46d

Le modulus, l’exposant public et la signature sont exprimés en hexadécimal.

Programme en python qui déchiffre la signature.

# -*- coding: cp1252 -*-
#!/usr/bin/env python
 
import sys
 
# n=modulus
n= int( \
"00BF37814A5AA901FFB3C70CF83166BDA4D6B3AE01B7F9AFEA087187D012A4D3" + \
"D446076279BB88D6E496BD2CFA46E37B8F" \
,16)
 
# exposant public
e = 0x10001
 
# signature
signature= int( \
"3FEE3986ECF4C8C0B82C2A157054C969F43843EC49C874BA8DDF8E1C6A8961EF" +\
"C7604B15D91C37E838A641FD4702D46D" \
,16)
 
# fonction pow :  (signature ^ e) mod n
hash=pow(signature,e,n)
print hex(hash)

On obtient ce résultat :

1ffffffffffffffffffffff003020300c06082a864886f70d020505000410645ab2943fea7ab54ebd99e6c5351398

La RFC 3447 chapitre 9.2 EMSA-PKCS1-v1_5 décompose la chaine selon le format suivant :
EM = 0x00 || 0x01 || PS || 0x00 || T

EM=0x00||0x01||PS                    ||0x00||T
EM=0x00||0x01||ffffffffffffffffffffff||0x00||3020300c06...9e6c5351398

PS: (Padding String) taille variable
Composé d’octets à 0xff. Longueur minimum 8 octets

Pour calculer sa taille : Len(EM) – Len(T)-3 = 48 – 34 – 3 = 11 octets

48 : Nombre d’octets composant le modulus. La taille du modulus est de 384 bits donc 48 octets.

T :
30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 05 05 00 04 10 || H
30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 05 05 00 04 10 || 645ab2943fea7ab54ebd99e6c5351398

La première partie codée en ASN1 signifie qu’il s’agit d’un hash MD5
H : correspond au HASH

Le parseur ASN.1 Editor va nous rendre l’information plus lisible.
On peut également utiliser la commande

openssl asn1parse -inform DER -in <NomDuFichier>

Le résultat est :

0:d=0  hl=2 l=  32 cons: SEQUENCE          
2:d=1  hl=2 l=  12 cons: SEQUENCE          
4:d=2  hl=2 l=   8 prim: OBJECT       :md5
14:d=2  hl=2 l=   0 prim: NULL              
16:d=1  hl=2 l=  16 prim: OCTET STRING  [HEX DUMP]:645AB2943FEA7AB54EBD99E6C5351398

La chaine T: 3020300c06082a864886f70d020505000410645ab2943fea7ab54ebd99e6c5351398 analysée par ASN.1 Editor devient :

certificat-hashmd5

On voit clairement :

  • L’OId : 1.2.840.113549.2.5 qui défini qu’il s’ agit d’ un hash md5.

  • Le hash MD5 : 645ab2943fea7ab54ebd99e6c5351398.

La RFC 3447 Appendice C : Module ASN.1 décrit l’OId md5 comme cela.

PKCS-1 {
       iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 5
}

Comment l’OId 1 2 840 113549 2 5 est-il encodé en 2a 86 48 86 f7 0d 02 05 ?
L’explication est donnée en annexe 1

Calcul du hash md5

Le fichier contenant le certificat est encodé base 64.

—–BEGIN CERTIFICATE—–
MIIB8DCCAaqgAwIBAgIJALlT51GHGFsFMA0GCSqGSIb3DQEBBAUAMDwxCzAJBgNV
BAYTAkZSMREwDwYDVQQHEwhMT0NBTElURTEaMBgGA1UEAxMRYmxvZy5jeWJlcmNv
ZC5jb20wHhcNMTMxMjMwMTIyNzI4WhcNMTQwMTI5MTIyNzI4WjA8MQswCQYDVQQG
EwJGUjERMA8GA1UEBxMITE9DQUxJVEUxGjAYBgNVBAMTEWJsb2cuY3liZXJjb2Qu
Y29tMEwwDQYJKoZIhvcNAQEBBQADOwAwOAIxAL83gUpaqQH/s8cM+DFmvaTWs64B
t/mv6ghxh9ASpNPURgdiebuI1uSWvSz6RuN7jwIDAQABo4GeMIGbMB0GA1UdDgQW
BBQDMemDN4HXxz6BdzJs3TNzoyCs4jBsBgNVHSMEZTBjgBQDMemDN4HXxz6BdzJs
3TNzoyCs4qFApD4wPDELMAkGA1UEBhMCRlIxETAPBgNVBAcTCExPQ0FMSVRFMRow
GAYDVQQDExFibG9nLmN5YmVyY29kLmNvbYIJALlT51GHGFsFMAwGA1UdEwQFMAMB
Af8wDQYJKoZIhvcNAQEEBQADMQA/7jmG7PTIwLgsKhVwVMlp9DhD7EnIdLqN344c
aolh78dgSxXZHDfoOKZB/UcC1G0=
—–END CERTIFICATE—–

Pour pouvoir calculer le hash md5, il faut d’abord décoder le certificat. Le certificat obtenu est au format asn.1

openssl enc -d -a -in cert.pem -out cert_asn1.txt

  • -d : decode
  • -a : base64
  • -in : certificat au format pem
  • -out : certificat décodé

Affichons dans un éditeur hexadécimal le fichier cert_asn1.txt obtenu.


00000000 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000 30 82 01 F0 30 82 01 AA A0 03 02 01 02 02 09 00 0‚.ð0‚.ª ......
00000010 B9 53 E7 51 87 18 5B 05 30 0D 06 09 2A 86 48 86 ¹SçQ‡.[.0...*†H
00000020 F7 0D 01 01 04 05 00 30 3C 31 0B 30 09 06 03 55 ÷......0<1.0...
00000030 04 06 13 02 46 52 31 11 30 0F 06 03 55 04 07 13 ....FR1.0...U..
00000040 08 4C 4F 43 41 4C 49 54 45 31 1A 30 18 06 03 55 .LOCALITE1.0...
00000050 04 03 13 11 62 6C 6F 67 2E 63 79 62 65 72 63 6F ....blog.cyberc
00000060 64 2E 63 6F 6D 30 1E 17 0D 31 33 31 32 33 30 31 d.com0...131230
00000070 32 32 37 32 38 5A 17 0D 31 34 30 31 32 39 31 32 22728Z..1401291
00000080 32 37 32 38 5A 30 3C 31 0B 30 09 06 03 55 04 06 2728Z0<1.0...U.
00000090 33 73 A3 20 AC E2 30 6C 06 03 55 1D 23 04 65 30 3s£ ¬â0l..U.#.e
000000A0 63 80 14 03 31 E9 83 37 81 D7 C7 3E 81 77 32 6C c€..1éƒ7.×Ç>.w2
000000B0 DD 33 73 A3 20 AC E2 A1 40 A4 3E 30 3C 31 0B 30 Ý3s£ ¬â¡@¤>0<1.
000000C0 09 06 03 55 04 06 13 02 46 52 31 11 30 0F 06 03 ...U....FR1.0..
000000D0 55 04 07 13 08 4C 4F 43 41 4C 49 54 45 31 1A 30 U....LOCALITE1.
000000E0 18 06 03 55 04 03 13 11 62 6C 6F 67 2E 63 79 62 ...U....blog.cy
000000F0 65 72 63 6F 64 2E 63 6F 6D 82 09 00 B9 53 E7 51 ercod.com‚..¹Sç
00000100 87 18 5B 05 30 0C 06 03 55 1D 13 04 05 30 03 01 ‡.[.0...U....0.
00000110 01 FF 30 0D 06 09 2A 86 48 86 F7 0D 01 01 04 05 .ÿ0...*†H†÷....
00000120 00 03 31 00 3F EE 39 86 EC F4 C8 C0 B8 2C 2A 15 ..1.?î9†ìôÈÀ¸,*
00000130 70 54 C9 69 F4 38 43 EC 49 C8 74 BA 8D DF 8E 1C pTÉiô8CìIÈtº.ߎ
00000140 6A 89 61 EF C7 60 4B 15 D9 1C 37 E8 38 A6 41 FD j‰aïÇ`K.Ù.7è8¦A
00000150 47 02 D4 6D 00 00 00 00 00 00 00 00 00 00 00 00 G.Ôm

Les parties bleus concernent la signature.

La partie rouge est l’ensemble des octets composant le certificat.

Si on calcule un hash md5 sur les octets en rouge on obtient le résultat suivant :

  • 645ab2943fea7ab54ebd99e6c5351398

Ce qui correspond bien au hash contenu dans la signature. Confirmation ci-dessous.

En rouge, tout ce qui est signé. On voit que c’est l’ ensemble des octets composant le certificat qui est pris en compte pour le calcul du hash md5


Certificate:
000Data:
00000Version: 3 (0x2)
0000Serial Number:
0000000b9:53:e7:51:87:18:5b:05
0000Signature Algorithm: md5WithRSAEncryption
0000Issuer: C=FR, L=LOCALITE, CN=blog.cybercod.com
00000Validity
000000Not Before: Dec 30 12:27:28 2013 GMT
000000Not After : Jan 29 12:27:28 2014 GMT
00000Subject: C=FR, L=LOCALITE, CN=blog.cybercod.com
00000Subject Public Key Info:
000000Public Key Algorithm: rsaEncryption
000000RSA Public Key: (384 bit)
000000000Modulus (384 bit):
0000000000000:bf:37:81:4a:5a:a9:01:ff:b3:c7:0c:f8:31:66:
00000000000bd:a4:d6:b3:ae:01:b7:f9:af:ea:08:71:87:d0:12:
00000000000a4:d3:d4:46:07:62:79:bb:88:d6:e4:96:bd:2c:fa:
0000000000046:e3:7b:8f
000000000Exponent: 65537 (0x10001)
0000X509v3 extensions:
000000X509v3 Subject Key Identifier:
00000000003:31:E9:83:37:81:D7:C7:3E:81:77:32:6C:DD:33:73:A3:20:AC:E2
000000X509v3 Authority Key Identifier:
000000000keyid:03:31:E9:83:37:81:D7:C7:3E:81:77:32:6C:DD:33:73:A3:20:AC:E2
000000000DirName:/C=FR/L=LOCALITE/CN=blog.cybercod.com
000000000serial:B9:53:E7:51:87:18:5B:05
000000X509v3 Basic Constraints:
000000000CA:TRUE
0Signature Algorithm: md5WithRSAEncryption
00003f:ee:39:86:ec:f4:c8:c0:b8:2c:2a:15:70:54:c9:69:f4:38:
000043:ec:49:c8:74:ba:8d:df:8e:1c:6a:89:61:ef:c7:60:4b:15:
0000d9:1c:37:e8:38:a6:41:fd:47:02:d4:6d

Exemple réel avec www.google.com. Je ne détaille pas. Ce sont les mêmes principes.

Google utilise

  • SHA1 : pour le calcul du hash.
  • RSA 2048 bits : pour la signature.

Déchiffrement de la signature du certificat de www.google.com.

Prérequis

  • Récupérer le modulus et l’exposant public de l’autorité de certification
    • Nom de l’ autorité de certification et du certificat:
      • The USERTRUST Network / UTN-USERFirst-Hardware
  • Récupérer la signature du certificat www.google.com

Modulus (2048 bits) :

b1f7c3383fb4a87fcf39825167d06d9fd2ff58f3e79f2bec0d895499b9389916
f7e0217948c2bb617412961d3c6a72d53c10673a39ed2b13cd66eb950933a46c
97b1e8c6ecc175799c465e8dabd06afdb92a55171054b319f09af6f1b15db6a7
6dfbe071176ba288fb00dffe1a31770c9a017ab132e32b0107386ec3a55e23bc
459b7b50c1c9308fdbe52b7ad35bfb33401ea0d59817bc8b87c389d35da08eb2
aaaaf68e698806c5fa8921f3089d692e09339b290d460f8ccc4934b06951bdf9
06cd68ad664cbc3eac61bd0a880ec8df3dee7c044c9d0a5e6b91d6eec7ed288d
ab4d878973d06ea4d01e168b14e17644037f63ace4cd499cc592f4ab32a1485b  

exposant public :
10001 (valeur hexa)

Signature :
Taille : 256 octets / 2048 bits

71c0993f5ef6bd33ff9e16cba8bfdd70f9d2533b36aec917c8ae5e4ddd62f7b7
d33e77a3fec07b32b5c994055250f25f3d7984494f5d6cb0d759bdd46c88fafc
c56586eb2852a242f67cbc6ac7072e25d1906220c68d51c22c45394e03daf718
e8cc0a3ad945d86c6e348b629c4e15f943eee597c03fad3513c52b06c741fde2
f77e45ad9bd1e166edf87a4b94397a2febe83f43d835d656fa74e76de6edac65
84fed04d0612deda59003c095ccf884be83db4152192cc6da651e28e97f1f482
46cbc4535eda5c9d659201658900e5b699ff2640f12f1931081ab16755860dae
353386bc974892d79660f8cefc96eb87c473cc949b585bf37aa42713d64ff469

Programme en python qui déchiffre la signature.

# -*- coding: cp1252 -*-
#!/usr/bin/env python
 
import sys
 
# n=modulus
n= int( \
"B1F7C3383FB4A87FCF39825167D06D9FD2FF58F3E79F2BEC0D895499B9389916" + \
"F7E0217948C2BB617412961D3C6A72D53C10673A39ED2B13CD66EB950933A46C" + \
"97B1E8C6ECC175799C465E8DABD06AFDB92A55171054B319F09AF6F1B15DB6A7" + \
"6DFBE071176BA288FB00DFFE1A31770C9A017AB132E32B0107386EC3A55E23BC" + \
"459B7B50C1C9308FDBE52B7AD35BFB33401EA0D59817BC8B87C389D35DA08EB2" + \
"AAAAF68E698806C5FA8921F3089D692E09339B290D460F8CCC4934B06951BDF9" + \
"06CD68AD664CBC3EAC61BD0A880EC8DF3DEE7C044C9D0A5E6B91D6EEC7ED288D" + \
"AB4D878973D06EA4D01E168B14E17644037F63ACE4CD499CC592F4AB32A1485B" \
,16)
 
# exposant publique
e = 0x10001
 
# signature
signature= int( \
"71C0993F5EF6BD33FF9E16CBA8BFDD70F9D2533B36AEC917C8AE5E4DDD62F7B7" + \
"D33E77A3FEC07B32B5C994055250F25F3D7984494F5D6CB0D759BDD46C88FAFC" + \
"C56586EB2852A242F67CBC6AC7072E25D1906220C68D51C22C45394E03DAF718" + \
"E8CC0A3AD945D86C6E348B629C4E15F943EEE597C03FAD3513C52B06C741FDE2" + \
"F77E45AD9BD1E166EDF87A4B94397A2FEBE83F43D835D656FA74E76DE6EDAC65" + \
"84FED04D0612DEDA59003C095CCF884BE83DB4152192CC6DA651E28E97F1F482" + \
"46CBC4535EDA5C9D659201658900E5B699FF2640F12F1931081AB16755860DAE" + \
"353386BC974892D79660F8CEFC96EB87C473CC949B585BF37AA42713D64FF469" \
,16)
 
# fonction pow : (signature ^ e) mod n
hash=pow(signature,e,n)
print hex(hash)

on obtient la chaine suivante :


0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffff0030213009
06052b0e03021a05000414b91238380d2b80b8ae8df2725c47fecbeae63c79

ff. . .ff: c’ est du PS (padding string)
Parsons la partie en bleu :

000 21: SEQUENCE {
002 9: SEQUENCE {
004 5: OBJECT IDENTIFIER sha1 (1 3 14 3 2 26)
00B 0: NULL
}
00D 14: OCTET STRING B9 12 38 38 0D 2B 80 B8 AE 8D F2 72 5C 47 FE CB EA E6 3C 79
}

On voit le type de hash utilisé : sha1
Le hash sha1 = B91238380D2B80B8AE8DF2725C47FECBEAE63C79

Calcul du hash :


00000000 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000 30 82 04 CC A0 03 02 01 02 02 11 00 F5 C8 6A F3 0‚.Ì .......õÈjó
00000010 61 62 F1 3A 64 F5 4F 6D C9 58 7C 06 30 0D 06 09 abñ:dõOmÉX|.0...
00000020 2A 86 48 86 F7 0D 01 01 05 05 00 30 81 97 31 0B *†H†÷......0.—1.
00|0000000000000000000000000000|0000000000000000000000000000000000|
00|0000000000000000000000000000|0000000000000000000000000000000000|
00000030 6F 64 6F 63 61 2E 63 6F 6D 30 25 06 03 55 1D 11 odoca.com0%..U..
00000040 04 1E 30 1C 82 0E 77 77 77 2E 67 6F 6F 67 6C 65 ..0.‚.www.google
00000050 2E 63 6F 6D 82 0A 67 6F 6F 67 6C 65 2E 63 6F 6D .com‚.google.com

le calcul du hash en sha1 donne bien : B91238380D2B80B8AE8DF2725C47FECBEAE63C79

ANNEXE ASN.1

Comment obtient-on l'Oid 1 .2. 840.113549. 2.5 à partir de la chaine 2a 86 48 86 f7 0d 02 05 ?

Principe du codage :

2a = 42d = 1 * 40 + 2 => 1d 2d
86 48 => 06 * 128 + 72d = 840d
86 f7 0d => (06 * 128 *128) + (119 * 128) + 13 = 98304 + 15232 + 13 = 113549d
02 = 2d
05 = 5d

On obtient bien : 1 .2. 840.113549. 2.5.

Explication :
(Penser à convertir les octets en décimal).

Le premier octet est divisé par 40. le quotient et le reste déterminent respectivement le 1er et le 2eme nœud de l’OId.
Pour les octets suivants deux cas possibles:
1er cas :

  • Si le 8eme bit de l’octet (le dernier bit à gauche) est positionné à 0 (0xxx xxxx), alors l’octet représente tout simplement un nœud de l’OId.
    • Exemple : 02 => 2

2ème cas :

  • Si le 8eme bit de l’octet (le dernier bit à gauche) est positionné à 1 (1xxx xxxx), alors il s’agit d’une suite.
    Il faut donc prendre en compte l’octet suivant pour calculer le nœud de l’OId.
    Si le 8ieme bit de l’octet suivant est également positionné à 1, il faudra prendre l’octet suivant pour calculer le nœud le l’OId
    et ainsi de suite. . .
    Le 8eme bit étant utilisé pour savoir si on a affaire à une suite, il n’est pas utilisé pour le calcul.
    Lors du calcul du nœud de l’OId, il faut positionner à 0 les 8eme bits de chaque octet (0xxx xxxx).
    Comme le 8ieme bit n’est pas utilisé, on est donc en base 128.
    Si l’on a une suite de 3 octets, on aura donc : (octet1 * 128 * 128) + (octet2 * 128) + octet3
 Publié par à 0 h 46 min
Oct 292013
 

Un article qui décrit l’ algorithme md5crypt qui est toujours

  • utilisé par les IOS CISO (secret 5)
    Exemple: $1$28772684$iEwNOgGugqO9.bIz5sk8k/

  • proposé par htpassword. Option -m
    Exemple: $apr1$VaN8AS0b$Z7hfqYrZ/.yOuxQepKEHe1

  • utilisé dans /etc/shadow (les distributions récentes n’ utilisent plus md5crypt mais sha512crypt).
    Exemple: $1$28772684$iEwNOgGugqO9.bIz5sk8k/

Algorithme

Le mot de passe et le « salt » sont fournis. Le « salt » est tronqué à 8 caractères

Calcul de Hash_A

On calcule un hash md5 selon l’algo suivant. je le nomme Hash_A. Le résultat est au format digest(raw).
|| signifie concaténation

hash_A = md5(password || salt || password)

Calcul de Hash_B

On calcule un deuxième hash md5 selon l’algo suivant. je le nomme Hash_B
Hash_A est utilisé pour calculer Hash_B.

Hash_B = md5(
password
||
$1$
||
salt
||
Hash_A (pour chaque bloc de 16 octets composant le password.
Si le password est inférieur à 16 octets, on passe a l’ étape suivante.
||
N octets de Hash_A pour les N octets restant du password.
||
représentation binaire de la longueur du password, en commençant par le bit de poids faible

si le bit est égale à 1 , on ajoute le premier caractère du password
si le bit est égale à 0 , on ajoute la valeur \x00 (null)
)

Calcul de Hash_C

Hash_B est utilisé pour calculer Hash_C.

Initialisation: Context_C est vide

Pour 1000 itérations (indice de 0 à 999 inclus)

si indice est impair alors Context_C = Context_C || password

si indice est pair

à l’itération 0 alors Context_C = Context_C || Hash_B
aux itération paires suivantes, Context_C = Context_C || (Hash_C précédent)

si l’indice n’est pas un multiple de 3 , Context_C = Context_C || salt
(pour savoir si c’est un multiple de 3, il suffit d’ utiliser la fonction modulo 3)

si l’indice n’est pas un multiple de 7 , Context_C = Context_C || password
(pour savoir si c’est un multiple de 7, il suffit d’utiliser la fonction modulo 7)

si l’indice est pair , Context_C = Context_C || password

si l’indice est impair

à l’itération 1 alors Context_C = Context_C || Hash_B
aux itérations impaires suivantes, Context_C = Context_C || (Hash_C précédent)

si indice est pair, Context_C = Context_C || password

Hash_C = md5 (Context_C)
Transposition et encodage base64 du Hash_C

Hash_C :        0, 1,  2, 3, 4,  5, 6, 7,  8, 9, 10 , 11, 12, 13, 14, 15  
Transposition : 0, 6, 12, 1, 7, 13, 2, 8, 14, 3,  9,  15,  4, 10,  5, 11

Découpage en groupe de 3 octets:

  • le premier groupe de 3 octets : position 0,6,12 du Hash_C
  • le deuxième groupe de 3 octets : position 1,7,13 du hash_C
  • le troisième groupe de 3 octets : position 2,8,14 du Hash_C
  • le quatrième groupe de 3 octets : position 3,9,15 du Hash_C
  • le cinquième groupe de 3 octets : position 4,10,5 du hash_C

Encodage base 64 des 16 octets en 22 caractéres (ajout de 4 bits de bourrage égales à 0)

Modèle d’encodage base64 :

          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123
./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

Pour le premier groupe de 3 octets


octet 0 0octet 6 0octet 12
aaaabbBB ccccCCCC DDddeeee

Découpage en 4 blocs de 6 bits


1er bloc : ddeeee
2ième bloc : CCCCDD
3ième bloc : BBBcccc
4ieme bloc : aaaabb

Même principe pour les 4 groupes de 3 octets suivants.

Pour le dernier octet

octet 11
AAaabbbb

Découpage en 2 blocs de 6 bits (bourrage à gauche avec 4 bits à 0)

(0000)AA aabbbb


1er bloc : aabbbb
2ieme bloc : 0000AA

Programme python

Pour finir un programme en python qui calcule un hash md5crypt
Vous devez fournir le salt et le password. Lignes [94,95] et [103,104].

Pour sélectionner tout le code , double-cliquez dans la fenêtre .

# -*- coding: cp1252 -*-
 
import hashlib
 
MAGIC = '$1$'   # Magic string
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 
def to64 (v, n):
   ret = ''
   while (n - 1 >= 0):
      n = n - 1
      ret = ret + ITOA64[v & 0x3f]
      v = v >> 6
   return ret
 
def apache_md5_crypt (pw, salt):
   return unix_md5_crypt(pw, salt, '$apr1$')
 
def unix_md5_crypt(pw, salt, magic=None):
 
   if magic==None:
      magic = MAGIC
 
   if salt[:len(magic)] == magic:
      salt = salt[len(magic):]
 
   # le "salt"  au maximum 8 caractères
   import string
   salt = string.split(salt, '$', 1)[0]
   salt = salt[:8]
 
   ctx = pw + magic + salt
 
   # hash_A
   final = hashlib.md5(pw + salt + pw).digest()
 
   for pl in range(len(pw),0,-16):
      if pl > 16:
         ctx = ctx + final[:16]
      else:
         ctx = ctx + final[:pl]
 
   # ctx = password + magic + salt + raw_hash_final (nombre de caractère du password)
 
   i = len(pw)
   while i:
      if i & 1:
         ctx = ctx + chr(0)
      else:
         ctx = ctx + pw[0]
      i = i >> 1
 
   # si le premier de la longueur (i) est à 1 , on ajoute \x00
   # si le premier de la longueur (i) est à 0 , on ajoute la première lettre du mot de passe
 
   # print hash_B
   final = hashlib.md5(ctx).digest()
 
   # itération pour lutter contre le bruteforce.
 
   for i in range(1000):
      ctx1 = ''
      if i & 1:
         ctx1 = ctx1 + pw
      else:
         ctx1 = ctx1 + final[:16]
 
      if i % 3:
         ctx1 = ctx1 + salt
 
      if i % 7:
         ctx1 = ctx1 + pw
 
      if i & 1:
         ctx1 = ctx1 + final[:16]
      else:
         ctx1 = ctx1 + pw
      final = hashlib.md5(ctx1).digest()
 
   passwd = ''
   passwd = passwd + to64((int(ord(final[0])) << 16) |(int(ord(final[6])) << 8) |(int(ord(final[12]))),4)
   passwd = passwd + to64((int(ord(final[1])) << 16) |(int(ord(final[7])) << 8) |(int(ord(final[13]))),4)
   passwd = passwd + to64((int(ord(final[2])) << 16) |(int(ord(final[8])) << 8) |(int(ord(final[14]))),4)
   passwd = passwd + to64((int(ord(final[3])) << 16) |(int(ord(final[9])) << 8) |(int(ord(final[15]))),4)
   passwd = passwd + to64((int(ord(final[4])) << 16) |(int(ord(final[10])) << 8)|(int(ord(final[5]))),4)
   passwd = passwd + to64((int(ord(final[11]))), 2)
 
   return magic + salt + '$' + passwd
 
if __name__ == "__main__":
 
   # mot de passe :  hashcat  -  hash : $1$28772684$iEwNOgGugqO9.bIz5sk8k/
   # unix et cisco
   # arguments
   password="hashcat"
   salt="28772684"
   print "unix ou cisco"
   # arg01: password
   # arg02: salt
   print unix_md5_crypt(password, salt)
 
   # apache
   # mot de passe :  hashcat  -  hash : $apr1$71850310$gh9m4xcAn3MGxogwX/ztb.
   # arguements
   password="hashcat"    
   salt="71850310"
   print "apache"
   # arg01: password
   # arg02: salt
   print apache_md5_crypt(password,salt)
 Publié par à 0 h 44 min
Oct 122013
 

Les hash des mots de passe sont stockés dans la table wp_users sous cette forme :

user_login user_password
admin $P$BXox1.zpleKOuW4jFS6/Fmg4iaiK.B/

Décomposition du hash: $P$BXox1.zpleKOuW4jFS6/Fmg4iaiK.B/

  • $P : algorithme
  • $B : nombre d’ itération
  • Xox1.zpl : sel aléatoire – longueur huit caractères
  • eKOuW4jFS6/Fmg4iaiK.B/ hash composé de 22 caractères

remarque : il n’ y a pas de séparateur entre le sel et le hash wordpress. Les longueurs sont fixes.
Le nom de l’ utilisateur n’ est pas utilisé dans le calcul du hash wordpress

Type de hash et itérations

B: détermine le nombre d’itération

Pour connaître le nombre d’ itération , on cherche dans un premier temps, la position du caractère dans la chaine suivante.

./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

Dans notre exemple, il s’ agit de B. B est en 13ieme position dans la chaine (l’index commence à 0).
la position de B défini l’ exposant.
2^13 = 8192

Algorithme de génération du hash

1ere phase : initialisation de la variable « hash »

hash = md5(salt | password)

concaténation du sel et du password puis hashage en md5

2eme phase : Réalisation de 8192 itérations (methode pour lutter contre le brute force)

Pour i de 0 à 8191
hash = md5(hash | password)
Fin Pour

Le nombre d’ itérations s’élève à 8192 . A chaque itération le hash est le hash de la précédente itération.

3eme phase: traitement pour rendre lisible le hash wordpress. Application d’ une fonction de type "base64"

Je vais m’ étendre sur cette 3ieme phase

encode64(hash)

Principe :

Le base64 est un codage de l’information utilisant 64 caractères.
On procède de gauche à droite, en concaténant 3 octets pour créer un seul groupement de 24 bits (8 bits par octet). Ils sont alors séparés en 4 nombres de seulement 6 bits (qui en binaire ne permettent que 64 combinaisons). Chacune des 4 valeurs est enfin représentée (codée) par un caractère simple et prédéfini de l’alphabet retenu. (Table ci-dessous.)

          1         2         3         4         5         6
0123456789012345678901234567890123456789012345678901234567890123
./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

Les deux premières lignes : indice de 0 à 63
la ligne suivante : les caractères

Le hash wordpress sera donc composé exclusivement de minuscules , de majuscules , de chiffre , du point et du slash.

Principe de codage :

Un hash md5 à une longueur de 16 octets. On découpe par groupe de 3 octets, ce qui nous fait 5 groupes. il reste 1 octets. nous verrons que ce dernier octet est traité un peu différemment.

traitement des groupes:

1er octet en bit : AAaa bbbb
2ieme octet en bit : cccc dddd
3ième octet en bit : eeee FFff

On concatène les 3 octets :

AAaa bbbb cccc dddd eeee FFff

Découpage par groupe de 6 bits selon le schémas suivant :

AAaa bbbb cccc dddd eeee FFff

1er groupe : aabbbb
2ieme groupe : ddddAA
3ieme groupe : ffcccc
4ieme groupe : eeeeFF

On traite les 4 autres groupes de 3 octets de la même manière.

Cas particulier : le dernier octet

Dernier octet : GGgg hhhh

Découpage par groupe de 6 bits selon le schémas suivant :
On concatène 4 bits à 0000 à GGgghhhh (0000GGgghhhh) de façon à avoir 2 x 6 bits.

0000GGgghhhh

1er groupe : gghhhh
2ieme groupe 0000GG

Le dernier octet est donc découpé en 2 caractères

Démonstration :

Éléments nécessaires pour la démonstration :

  • hash en string hexa obtenu apres les 8192 itérations
    aaa5e9a2f1461e1244326bb8a66b014d

  • hash wordpress
    eKOuW4jFS6/Fmg4iaiK.B/

Découpage des 3 premiers octets :

a   a    a   5    e   9
10101010 10100101 11101001 

AAaa bbbb cccc dddd eeee FFff


1 aabbbb = 101010 = 42d = "e"»
2 ddddAA = 010110 = 22d = "K"
3 ffcccc = 011010 = 26d = "O"
4 eeeeFF = 111010 = 58d = "u"

Les 4 premiers caractères sont : eKOU ce qui correspond au 4 premiers caractère du hash wordpress de notre exemple. (eKOuW4jFS6/Fmg4iaiK.B/

Pour le dernier caractère :

Découpage

4   d
01001101

On ajoute 4 bits à 0 devant

000001 001101

0000GG gghhhh


1 gghhhh = 001101 = 13d = "B"
2 0000GG = 000001 = 1d = "/"

les 2 derniers caractères sont : B/, ce qui correspond bien au 2 derniers caractère du hash wordpress de notre exemple. (eKOuW4jFS6/Fmg4iaiK.B/)

Brute force

Le programme hashcat peut être utilisé pour brute forcer les hash wordpress.

Dans hashcat, le commutateur -m permet de définir l’ algo à utilisé.

Définir -m400. Ce qui correspond selon la documentation à l’ algo utilisé par WordPress.

Programme python

Pour finir un programme en python qui calcule le hash wordpress.
Vous devez fournir le salt et le password. Lignes 47 et 48 .

# -*- coding: utf-8 -*-
 
import hashlib
import binascii
 
#
# fonctions
#
 
def _hash_encode64(hash, itoa64):
   output=""
   i=0
   while True:
       value = ord(hash[i]);
       i+=1
       output = output + str(itoa64[value & 63])
 
       if (i<16):
          value = value | ord(hash[i]) << 8
 
       output = output + str(itoa64[(value >> 6) & 63])
 
       i+=1
       if (i >= 16):
          print i
          break
 
       if (i < 16):
          value = value | (ord(hash[i]) << 16)
 
       output = output + str(itoa64[(value >> 12) & 63])
 
       i+=1
       if (i >= 16):
          break;
       output = output + str(itoa64[(value >> 18) & 63])
 
   return output
 
#                   1         2         3         4         5         6
#         0123456789012345678901234567890123456789012345678901234567890123
itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 
#arg01 : salt
#arg02 : pass
 
salt = "Xox1.zpl"
password = "password"
#resultat : $P$BXox1.zpleKOuW4jFS6/Fmg4iaiK.B/
hash = hashlib.md5(salt + password).hexdigest()
print hash
hash = hashlib.md5(salt + password).digest()
 
for i in range(0,8192) :
   hash = hashlib.md5(hash + password).digest()
 
print binascii.hexlify(hash)
 
P= _hash_encode64(hash, itoa64)
print "$P$B" + salt +  P +  "\n"    
 Publié par à 21 h 44 min
Oct 032013
 

Crypto PBKDF2 Grub2

Cet algorithme est utilisé pour lutter contre le brute force de mots de passe (ou clefs) . Il augmente considérablement les temps de calculs pour obtenir un hash.

on le retrouve notamment dans :

grub2
wpa
truecrypt

Dans ces 3 exemples, PBKDF2 repose sur l’algorithme cryptographique HMAC que l’on combine dans une fonction (F) itérative à base de xor.

Théorie

DK = PBKDF2(PRF, Password, Salt, itérations, dkLen)

PRF : PRF est une fonction pseudo-aléatoire composé de deux paramètres.
on utilise généralement HMAC comme PRF

Password : le mot de passe en clair

Salt: le sel

itérations : nombre d’itération

dkLen : la longueur en octets du résultat (DK – Derived Key)

Dk : Le résultat appelé DerivedKey => le hash du mot de passe

Formule

F( Password, Salt, c, i ) = U1 xor U2 xor... ...xor Uc

c = nombre d’ itération
i = index itération

U1 = PRF(Password, Salt || i)
U2 = PRF(Password, U1)
Uc = PRF(Password, Uc-1)

La fonction F réalise un « xor » entre les différentes chaines U1 à Uc.
c est égale au nombre d’ itérations.

Calcul de U1 (1ere itération)

  • 1er paramètre : password
  • 2ieme paramètre : salt || i ( || signifie concaténation )
  • i est l’ index d’ itération, à la première itération i vaut 1
  • i est codé sur 4 octets (big-endian 32 bits) cela devient : "\x00\x00\x00\x01"
  • Ces 2 paramètres servent à calculer U1

Calcul de U2.....Uc( itérations suivantes):

  • 1er paramètre : password ( constant, ne change pas jusqu’à la dernière itération)
  • 2ieme paramètre : correspond à U1(calculé précédemment )

et on réalise un xor entre les Ux comme ceci
entre U1 et U2
puis ensuite entre le résultat précédent (U1 xor U2) et U3
et ainsi de suite

Exemple pratique – Grub2

La suite de cet article est basée sur le protocole de hashage de mots de passe utilisé par GRUB2.

par défaut, grub2 exécute (10 mille) itérations et la longueur du salt est égale à 64 octets.

La commande grub-mkpasswd-pbkdf2 permet de générer un hash pbkdf2
exemple de résultat :

pbkdf2-01

Détaillons chaque champs:

  • Le mot de passe que j’ ai saisi est : 12345678

  • sha512 : pbkdf2 utilise le protocole sha512 comme PRF

  • 10000 : nombre d’ itérations (10 mille)

  • salt : 64 octets

pbkdf2-02

  • le hash du mot de passe : 64 octets

pbkdf2-03

Les champs sont séparés par des points (.).
Par souci de clarté j’ ai découpé chaque chaine en 4 partie , mais en réalité tout est sur une même ligne.

Rappel :

DK = PBKDF2(PRF, Password, Salt, itérations, dkLen)

Dans le cadre de grub2 :

  • la fonction Prf est la fonction cryptographique HMAC-SHA512.
  • Le nombre d’ itérations est fixé à 10000
  • le résultats est sur 64 octets

Donc la formule devient :

DK = PBKDF2(HMAC-SHA512, password, salt, 10000, 64)

calcul de la 1ere itération :
U1 = HMAC-SHA512("12345678","\0x2D\0xD6.....\0xDE\x08\x00\x00\x00\x01")
Rappel : l’indice (\x00\x00\x00\x01) est concaténé au salt

calcul 2ieme itération et itérations suivantes :
U2 = HMAC-SHA512("12345678",U1)
...
...
Uc = HMAC-SHA512("12345678",Uc-1 )

Programme écrit en python qui génère un hash PBKDF2

Programme écrit en python qui génère un hash PBKDF2

j’ ai testé ce programme sur kali 1.0.3.

Généré un hash avec la commande grub2 ci-dessous , saisir le mot de passe « 12345678 » :

grub-mkpasswd-pbkdf2 -s 3

-s : nombre d’ octets du salt

important:
Dans cet exemple j’ai réduit longueur du salt pour éviter par la suite des saisis au clavier fastidieuses.

Avant de lancer le programme, repérer la ligne 51 (surlignée) pour déclarer les bons paramètres. Vous devriez normalement changer uniquement le salt ( en bleu).
resultat = PBKDF2("12345678","\xF2\xB0\xF0",10000,64)

Pour sélectionner tout le code , double-cliquez dans la fenêtre.

# -*- coding: utf-8 -*-
 
import struct
import timeit
import hashlib
import hmac
import binascii
 
# fonctions
 
def hmacfunc(key,message):
    # key = mot de passe
    # message = salt
    return hmac.new(key,message,hashlib.sha512).digest()
 
def xor_string(str1, str2):
    # TODO: slow!
    str3 = ''
    for i in xrange(len(str1)):
        str3 += chr(ord(str1[i]) ^ ord(str2[i]))
    return str3
 
def PBKDF2(password, salt, iterations, derivedlen):
 
        def F(P, S, c, i):
            U_prev = hmacfunc(P, S + struct.pack('>L', i))
            res = U_prev
            for cc in xrange(2, c+1):
                U_c = hmacfunc(P, U_prev)
                res = xor_string(res, U_c)
                U_prev = U_c
            return res
        tmp = ''
        i = 1
        while True:
            tmp += str(F(password, salt, iterations, i))
            if len(tmp) > derivedlen:
                break
            i += 1
 
        return tmp[0:(derivedlen)]
 
# grub-mkpasswd-pbkdf2 -s 3
# pensez à  changer le mot de passe,le salt et le nombre d' iteration
# dans cet exemple
#  le mot de passe est "password"
#  le salt est "\x4D\x06\x01"
#  le nombre d' itération est  : 10000
resultat =  PBKDF2("123", "\xA2\xC8\x73", 10000, 64)
 
#arg01 : mot de pase
#arg02 : salt
#arg03 : iteration
#arg04 : taille en octets du resultat
 
# commande a taper en ligner de commande pour timer la durée de la fonction
# pbkdf2 est le nom du programme a timer (sans l' extension .py)
 
#import timeit
#from pbkdf2 import *
#timeit.Timer(stmt='PBKDF2("password", "\xF2\xB0\xF0", 10000, 64)',setup='from pbkdf2 import PBKDF2,hmacfunc').timeit(1)
 
print "resultat " + binascii.hexlify(resultat).upper()

Le protocole WPA2 utilise également le protocole PBKDF2. Voici les paramètres utilisés

DK = PBKDF2(HMAC−SHA1, passphrase, ssid, 4096, 256)

la fonction PRF est dans ce cas HMAC-SHA1 le ssid fait office de salt le nombre d’ itération s’élève à 4096 le taille du résultat est égale à 256 octets.

Un challenge pour la fin

Enoncé : grub-mkpasswd-pbkdf2 -s 3 password : ???

#
# Your PBKDF2 is grub.pbkdf2.sha512.10000.BBD751.AFC96786A477E787381FBB4AE0B42DAA2E56B25C0A285C653C3452D0A8A31FD42E83736E53BE1CA8380E4C90C791AFA80ACE59DCB285E0EB2F9D018B1D55233E
#

le mot de passe à trouver est composé de 3 chiffres (001 à 999)

 Publié par à 21 h 47 min