M1 Cryptographie - Cryptographie asymétrique

La cryptographie classique (symétrique) permet à deux utilisateurs A (Alice) et B (Bob) de communiquer confidentiellement en partageant une clef secrète $K$. Ainsi, Eve (eavesdropper) qui intercepte la communication ne peut pas la déchiffrer.

Le problème qui s'est immédiatement posé avec l'apparition des réseaux informatiques est celui de la distribution des clefs. Avec un réseau de $N$ noeuds, tous susceptibles de communiquer entre eux ou d'intercepter le traffic, chaque utilisateur devrait posséder $\frac{N(N-1)}{2}$ clefs, une pour chaque lien bidirectionnel, ce qui est évidemment impossible.

En 1976, dans un article historique, Diffie et Hellman ont proposé (sans savoir à l'époque comment la réaliser), l'idée de cryptographie à clef publique, avec laquelle chaque utilisateur du réseau posséderait deux clefs, une publique $K_P$, accessible à tous les autres, et une secrète $K_S$, connue de lui seul.

On devrait pouvoir chiffrer un message destiné à un utilisateur en connaissant seulement sa clef publique, mais ne pouvoir déchiffrer le cryptogramme qu'en connaissant en plus sa clef secrète.

Plusieurs systèmes ont alors été proposés pour satisfaire ces contraintes, apparemment inconciliables. L'idée générale est d'utiliser des problèmes algorithmiquement difficiles, mais dont certaines instances sont faciles.

Le système de Merkle-Hellman

Le premier système proposé, dû à Merkle et Hellman, reposait sur le problème du sac-à-dos : il est généralement difficile de savoir si un grand nombre $N$ peut s'écrire comme somme d'une sous-liste d'une liste de nombres donnée, mais le problème est trivial si la liste en question est par exemple $1,2,4,8,\ldots,2^m$ : cela revient à écrire $N$ en binaire. Il existe d'autres instances faciles, les suites super-croissantes, c'est à dire, dans lesquelles chaque terme est strictement supérieur à la somme des précédents.. On en choisit une qu'on garde secrète, $A=[a_0,a_1,\ldots,a_{n-1}]$ et on publie $B=[b_0,b_1,\ldots,b_{n-1}$ où $b_i=ka_i\mod M$ où $M$ est un grand nombre $>a_0+a_1+\cdots+a_n$, et $k$ un entier inversible modulo $M$. On pose $d=k^{-1}\mod M$.

La clef publique est $B$, la clef secrète est $(A,k,M)$. Les messages sont des blocs de $n$ bits.

Le cryptogramme de $$m = (m_0,\ldots,m_{n-1})\in \{0,1\}^n$$ est $$c = \sum_{i=0}^{n-1}m_i b_i$$ Pour déchiffrer, le destinataire calcule $$ s = lc\mod M = \sum_{i=0}^{n-1}m_ia_i$$ et retrouve les $m_i$ en résolvant le problème facile.

Ce système a été rapidement craqué, et par conséquent abandonné.

RSA

Le premier système à résister sérieusement a été RSA, initiales de Rivest-Shamir-Adelman. Il est encore le plus utilisé. Il repose sur la difficulté de la factorisation des grands entiers. Si on multiplie deux grands nombres premiers $p$ et $q$, il est impossible en pratique de retrouver $p$ et $q$ à partir de $n=pq$.

Pour construire une clef RSA, on tire au hasard deux nombres premiers $p<q$ de $N$ bits, et on calcule $n=pq$ (le module). On calcule aussi $\varphi(n)=(p-1)(q-1)$ (indicatrice d'Euler).

On choisit ensuite un exposant public $2\lt e\lt n-1$, qui doit être inversible modulo $\varphi(n)$, et on calcule son inverse $d = e^{-1}\mod \varphi(n)$ (l'exposant privé). On a donc $ed=1+k\varphi(n)$ pour un certain $k$.

Dans la description originale, $d$ est choisi au hasard et $e$ calculé ensuite, mais pour des raisons pratiques on utilise souvent les valeurs $e=3,17,257$ ou $65537$.

La clef publique est $(n,e)$, la clef secrète est $(p,q,d)$.

Les messages à chiffrer sont des blocs de $2N$ bits, interprétés comme des entiers modulo $n$. Pour chiffrer un tel message $m$, on calcule $$ c= m^e \mod n.$$ Pour déchiffrer $c$, le destinataire calcule $$ c^d \mod n = m^{ed} \mod n = m^{1+k\varphi(n)} \mod n = m^1 (m^{\varphi(n)})^k \mod n = m$$ au moins si $m$ est premier avec $\varphi(n)$ d'après le théorème d'Euler, et en fait dans tous les cas, à cause de la forme spéciale de $n$.

On a $$c^d = m(m^{k(q-1)})^{p-1}\equiv m \mod p$$ $$c^d = m(m^{k(p-1)})^{q-1}\equiv m \mod q$$ d'après le théorème de Fermat, et on peut donc retrouver $m$ à partir de $m_p = c^d\mod p$ et $m_q=c^d\mod q$ en appliquant le théorème des reste chinois.

En pratique, on complète la clef secrète avec $$d_p = d\mod (p-1)$$ $$d_q = d\mod (q-1)$$ $$q_{inv} = q^{-1}\mod p$$ et le déchiffrement est obtenu en calculant $$m_1 = c^{d_p}\mod p$$ $$m_2 = c^{d_q}\mod q$$ $$h = (m_1-m_2)q_{inv} \mod p$$ $$ m = m_2+hq.$$

In [1]:
# Construisons une clef RSA de 512 bits
from random import randrange
from ent3 import *
In [2]:
a = randrange(2*255,2**256-1)
if not a%2: a+=1
a
Out[2]:
26500187408045894646773779047611999515297724476034815358742346965632430907347
In [3]:
while not miller_rabin(a):a+=2
In [4]:
p=a
In [5]:
b = randrange(p,2**256-1)
if not b%2: b+=1
while not miller_rabin(b):b+=2
In [6]:
q=b 
In [7]:
print ('%x' % p)
print ('%x' % q)
3a969315486e392c3dfbeb9046d7a25e2cefd3ed32fb68dd5f26ac5564e8204f
d0c3fdec83717e5c79b926bb62a3dc585a5930cb147ba0f9aac225809073f587
In [8]:
n = p*q
print (n)
2502342741777978305394161689835161800308635593678070233706745739850497379826016189955751135672383376951956116000952438972760111920886781584882452875289769
In [9]:
phi_n = (p-1)*(q-1)
e = 257
d = inversemod(e,phi_n)
print (d)
1129462093565157523057286988408088594691835520881930533501877454562870412684061674861262502135092940232711653086400913818719717504109788414970713354381073
In [10]:
d1, d2, q_inv  = d % (p-1), d % (q-1), inversemod(q,p)
In [11]:
print (d1)
print (d2)
print (q_inv)
20519600366541373675906544865660653321183841131248748079337459323583088523683
734843270047991781736235626626299541236651911309911826448204008511863643661
16059359551391899448053333558918060785093890179516929488208050776761986861230
In [12]:
from codecs import encode, decode
def text2int(t):
    assert len(t) <= 64 # 64*8 = 512 bits
    if isinstance(t,str):
        return int(encode(t.encode(),'hex'), 16) # t.encode() nécessaire en Python 3 pour avoir des bytes
    elif isinstance(t,bytes):
        return int(encode(t,'hex'), 16)
    

def int2text(m):
    s = '%x' % m
    if len(s)%2: s = '0'+s
    return decode(s, 'hex')
In [13]:
msg = "L'arithmétique, c'est bien difficile !"
m = text2int(msg)
print (m)
2482049485033484295509148869034669333161266776700492625573073315249287942938347748044373827617
In [14]:
msg.encode() # l'encodage par défaut est celui du système 
Out[14]:
b"L'arithm\xc3\xa9tique, c'est bien difficile !"
In [15]:
msg.encode('utf8')
Out[15]:
b"L'arithm\xc3\xa9tique, c'est bien difficile !"
In [16]:
# Chiffrement
c = pow(m,e,n)
print (c)
1424921898341682605762331838499604971017849285555226544151583141753235703470686191844906762174964147363721712002777200150575835094876121346964116166603823
In [17]:
# Déchiffrement
test = pow(c,d,n)
print (test)
int2text(test)
2482049485033484295509148869034669333161266776700492625573073315249287942938347748044373827617
Out[17]:
b"L'arithm\xc3\xa9tique, c'est bien difficile !"
In [18]:
print(_.decode('utf8'))
L'arithmétique, c'est bien difficile !
In [19]:
# Déchiffrement accéléré avec les paramètres précalculés
m1, m2 = pow(c,d1,p), pow(c,d2,q)
h = (m1-m2)*q_inv % p
test2 = m2 + h*q
print (test2)
print (int2text(test2).decode('utf8'))
2482049485033484295509148869034669333161266776700492625573073315249287942938347748044373827617
L'arithmétique, c'est bien difficile !
In [20]:
print (encode(msg.encode(),'hex'))
print ('%x'%n)
b'4c2761726974686dc3a974697175652c206327657374206269656e20646966666963696c652021'
2fc732504450614349ff94bc8282afec9a4bd4d96ecaf31e0e32634c0e37f62fbd0a487f2196ac8df264720fe7b61e6be22857cf82cc9498960b138d50d1a4a9

Notre message était sensiblement plus court que la clef. En pratique, pour éviter de chiffrer des blocs de zéros, qui risqueraient de révéler des indications sur la clef secrère, et pour éviter que deux messages identiques soient chiffrés de la même manière, on remplace les octets nuls par un bourrage aléatoire, respectant une norme telle que PKCS #1.

Par exemple, avec un module de $k$ octets, on transmettra des entiers $D$ de moins de $k-11$ octets, sous forme de blocs de $k$ octets ayant la structure

00 02 ||bourrage||00||D

où le bourrage est consitué d'octets aléatoires non nuls.

In [21]:
def pad(D,k,random=True):
    assert len(D)<k-11
    m = k-3-len(D)
    s = open('/dev/urandom','rb').read(m).replace(b'\x00',b'\xff')
    res = b'\x00\x02'+s+b'\x00'+D
    return res

D = msg.encode()

P = pad(D,64); P
Out[21]:
b"\x00\x02\xab\xd2\xf7P\xdf\t\x9eM\xa5\xf0\xba\xd6\xf8\xc7\xb4\xe9k\xec\xdbF\xcf\\\x00L'arithm\xc3\xa9tique, c'est bien difficile !"
In [22]:
len(D)
Out[22]:
39
In [23]:
m = text2int(P)
m
Out[23]:
546490073573023782919833310723505366415935528325926911697419548819970651491447084662782218655660251683607633278569570342040339125483138841796956987425
In [24]:
c = pow(m,e,n)
c
Out[24]:
2295163261149511403305503728933624368514007681287606939863821243050574765579241828142902110121753137764180975920166834008405867299008418437194186758123539
In [25]:
test3 = pow(c,d,n)
int2text(test3)
Out[25]:
b"\x02\xab\xd2\xf7P\xdf\t\x9eM\xa5\xf0\xba\xd6\xf8\xc7\xb4\xe9k\xec\xdbF\xcf\\\x00L'arithm\xc3\xa9tique, c'est bien difficile !"

RSA en pratique

Le premier endroit où observer des clefs RSA sur un système Unix est le répertoire .ssh d'un utilisateur.

Avant un première utilisation, on crée les clefs par la commande

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/jyt/.ssh/id_rsa): ./.ssh/id_rsa
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in ./.ssh/id_rsa.
Your public key has been saved in ./.ssh/id_rsa.pub.
The key fingerprint is:
56:1d:12:06:7d:4d:96:52:1d:83:d5:de:c6:3b:39:f7 jyt@scriabine
The key's randomart image is:
+--[ RSA 2048]----+
|        .o+..+**o|
|         ..oo+o +|
|          .... o.|
|         .      =|
|        S      .o|
|       .       =.|
|                =|
|                E|
|                 |
+-----------------+
$ ls .ssh
id_rsa  id_rsa.pub

On n'a pas précisé de paramètres, la commande a donc crée par défaut une clef RSA de 2048 bits. La clef secrète est dans id_rsa, la clef publique dans id_rsa.pub.

Examinons ces fichiers.

In [27]:
!cat .ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/rbeMXySUYh1A728Vocv9oaijeVOUF9sKzqUBVfAR6PNXtaMYqY49gFM/YvlYSDKwmurF1sVMts9DWS1ofAbu1TqqOScGex2bbVrCfhxWyEcT5PlYnPYBlaJ7/1USbeHWgeFHIArfOzeknEvjsecwoxjOgzRDCRD9q9Pa35PX9IDgGF5z2ojAG5p84sYtJaLvHiSDUxfjZ/0d2aNJ6vKQ7jJhNJxqGmFpwQ+XKSIZ/oS1OffCMvn/cQGXjc14Litl1hdQf8pky6r6TPdUYS62o1qjDsFs1BB9xFnRGDH9BkWaHdychICHsUy3nmBsYgxqMwAfg1olwYNs5Vrqca7x jyt@elzet
In [28]:
!cat .ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAv623jF8klGIdQO9vFaHL/aGoo3lTlBfbCs6lAVXwEejzV7Wj
GKmOPYBTP2L5WEgysJrqxdbFTLbPQ1ktaHwG7tU6qjknBnsdm21awn4cVshHE+T5
WJz2AZWie/9VEm3h1oHhRyAK3zs3pJxL47HnMKMYzoM0QwkQ/avT2t+T1/SA4Bhe
c9qIwBuafOLGLSWi7x4kg1MX42f9HdmjSerykO4yYTScahphacEPlykiGf6EtTn3
wjL5/3EBl43NeC4rZdYXUH/KZMuq+kz3VGEutqNaow7BbNQQfcRZ0Rgx/QZFmh3c
nISAh7FMt55gbGIMajMAH4NaJcGDbOVa6nGu8QIDAQABAoIBAHgxLyJXWrGs4Fki
io6O+UIeh4eSgaUgXFrnfzJaOAKTB1wdapsBX08TU6AwqNgB1b9GNSc/aFKVY1wA
5GdbNmG21WV+FwmKU+Nta/b/azfDuEYyU2SMb/pIYS3NywOWYYHHyYJ3Bjo6gMa4
tyGdIbIu41RDk5bhbYUTpPHfNm64LfTM/ZZUwfZGzPCv3VPDXLy2ws2NWWRcAyBH
F6hMpS/ARTHKrssjz2MvvF0BmcvDRK1D//hmXbbK7LXJsjR63YCpFSAepXfrktyf
8ILrnnatfmoO9DyEQQ56kceGALbyE/zy6frqw74MnTwoiLdiZxGeY4YIezWYL8SR
HwwFOpECgYEA3aqnqAFbRLbzv5fU56gTO63EzOgCYN+2P0WrnH0BKK7AySxrtDgj
4vhEoI5QpBZ42H9pza3WZNFZ1hCnIIH/EXR/ZIOX8N+TMus8d8d378JBlg36W8F8
tEWjn6kW6kY8Yyjw7Hs+3jYqnDeIETeIQzzcm7pRViH8xw7NHbO3z+UCgYEA3V4A
LMe9MOrVBT1YfFboQGuYGdO9qZRL4wH1oBe+9tbP3p5rLL7OsMid64A9aS4ebdGa
CPlBUM3tr1NbHez9JbgpNLooCNLNV5iJn4iMt5yAyKpZ3DAwFgorTZ6iVdfAAUi1
o02Qq407va7tjpd/9ded/I9U2wfT4Jy1L86ceh0CgYBbNfiM6hn7GWkNAlXqCL/5
Q5SCWEl6QTOFr45g8xMCAX50iSG8Y4lowI3EnyrRiimptCv+JTTeAUL9EZcjijpB
nXU6D+f6hpTUU/VquBpC/uTr8M5+6Qv+RdWBQhuaxNHeX59bP49r8k/wPe1wYDBi
sm14at9DGPMhmZaPTT8qfQKBgQC+saNk8AvCgAlRoi7/rb4VAJreZNEVrHJS8/Us
HEidSx92nvGkchqLn8aqgKZmXRxJbi5LXK0vdrYyOpRbizPnsmWMznB+aVoLA5RK
oc7WvTMTqewPClPiKJB1JRqi6GC2unP+YWsm3VuBY5exJkFM/plSYAaxSGT1MQnE
TS/u4QKBgHhygHlkyxpbcOGUAj/4puB1lLB6gzZfbDQVxi95VLBeR33+DUCi2FNR
tmZoYJqsSO1G5DsEa7p4locjVjEMOGLmxm2vDlv6A6Rgip4l31OywMj/oMJVBpAM
C3xh7uUSbfhMYRWWEcD+1X/7Nfmr2g7AswqLbzjDFU6o8POjV66m
-----END RSA PRIVATE KEY-----

Ils sont manifestement encodés en base64. Essayons de déchiffrer leur contenu, en commençant par la clef publique qui est plus simple.

In [29]:
p = open('.ssh/id_rsa.pub','rb').read()
In [30]:
kp = decode(p[p.index(b'A'):p.index(b'jyt')], 'base64') # on découpe le morceau intéressant
kp
Out[30]:
b'\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x01\x00\xbf\xad\xb7\x8c_$\x94b\x1d@\xefo\x15\xa1\xcb\xfd\xa1\xa8\xa3yS\x94\x17\xdb\n\xce\xa5\x01U\xf0\x11\xe8\xf3W\xb5\xa3\x18\xa9\x8e=\x80S?b\xf9XH2\xb0\x9a\xea\xc5\xd6\xc5L\xb6\xcfCY-h|\x06\xee\xd5:\xaa9\'\x06{\x1d\x9bmZ\xc2~\x1cV\xc8G\x13\xe4\xf9X\x9c\xf6\x01\x95\xa2{\xffU\x12m\xe1\xd6\x81\xe1G \n\xdf;7\xa4\x9cK\xe3\xb1\xe70\xa3\x18\xce\x834C\t\x10\xfd\xab\xd3\xda\xdf\x93\xd7\xf4\x80\xe0\x18^s\xda\x88\xc0\x1b\x9a|\xe2\xc6-%\xa2\xef\x1e$\x83S\x17\xe3g\xfd\x1d\xd9\xa3I\xea\xf2\x90\xee2a4\x9cj\x1aai\xc1\x0f\x97)"\x19\xfe\x84\xb59\xf7\xc22\xf9\xffq\x01\x97\x8d\xcdx.+e\xd6\x17P\x7f\xcad\xcb\xaa\xfaL\xf7Ta.\xb6\xa3Z\xa3\x0e\xc1l\xd4\x10}\xc4Y\xd1\x181\xfd\x06E\x9a\x1d\xdc\x9c\x84\x80\x87\xb1L\xb7\x9e`lb\x0cj3\x00\x1f\x83Z%\xc1\x83l\xe5Z\xeaq\xae\xf1'

On voit que la structure décodée se compose des 4 octets 0,0,0,7, de la chaîne « ssh-rsa ». Il y a ensuite un entier sur 32 bits en big-endian donnant la longueur de l'exposant public e (en octets, ici 3) , suivi de sa valeur, puis de la longueur de n (même système) et de sa valeur. On peut décoder avec le module struct sans trop de difficulté :

In [31]:
import struct
ll = struct.unpack('!4B7sI3BI257c',kp)
print (ll)
(0, 0, 0, 7, b'ssh-rsa', 3, 1, 0, 1, 257, b'\x00', b'\xbf', b'\xad', b'\xb7', b'\x8c', b'_', b'$', b'\x94', b'b', b'\x1d', b'@', b'\xef', b'o', b'\x15', b'\xa1', b'\xcb', b'\xfd', b'\xa1', b'\xa8', b'\xa3', b'y', b'S', b'\x94', b'\x17', b'\xdb', b'\n', b'\xce', b'\xa5', b'\x01', b'U', b'\xf0', b'\x11', b'\xe8', b'\xf3', b'W', b'\xb5', b'\xa3', b'\x18', b'\xa9', b'\x8e', b'=', b'\x80', b'S', b'?', b'b', b'\xf9', b'X', b'H', b'2', b'\xb0', b'\x9a', b'\xea', b'\xc5', b'\xd6', b'\xc5', b'L', b'\xb6', b'\xcf', b'C', b'Y', b'-', b'h', b'|', b'\x06', b'\xee', b'\xd5', b':', b'\xaa', b'9', b"'", b'\x06', b'{', b'\x1d', b'\x9b', b'm', b'Z', b'\xc2', b'~', b'\x1c', b'V', b'\xc8', b'G', b'\x13', b'\xe4', b'\xf9', b'X', b'\x9c', b'\xf6', b'\x01', b'\x95', b'\xa2', b'{', b'\xff', b'U', b'\x12', b'm', b'\xe1', b'\xd6', b'\x81', b'\xe1', b'G', b' ', b'\n', b'\xdf', b';', b'7', b'\xa4', b'\x9c', b'K', b'\xe3', b'\xb1', b'\xe7', b'0', b'\xa3', b'\x18', b'\xce', b'\x83', b'4', b'C', b'\t', b'\x10', b'\xfd', b'\xab', b'\xd3', b'\xda', b'\xdf', b'\x93', b'\xd7', b'\xf4', b'\x80', b'\xe0', b'\x18', b'^', b's', b'\xda', b'\x88', b'\xc0', b'\x1b', b'\x9a', b'|', b'\xe2', b'\xc6', b'-', b'%', b'\xa2', b'\xef', b'\x1e', b'$', b'\x83', b'S', b'\x17', b'\xe3', b'g', b'\xfd', b'\x1d', b'\xd9', b'\xa3', b'I', b'\xea', b'\xf2', b'\x90', b'\xee', b'2', b'a', b'4', b'\x9c', b'j', b'\x1a', b'a', b'i', b'\xc1', b'\x0f', b'\x97', b')', b'"', b'\x19', b'\xfe', b'\x84', b'\xb5', b'9', b'\xf7', b'\xc2', b'2', b'\xf9', b'\xff', b'q', b'\x01', b'\x97', b'\x8d', b'\xcd', b'x', b'.', b'+', b'e', b'\xd6', b'\x17', b'P', b'\x7f', b'\xca', b'd', b'\xcb', b'\xaa', b'\xfa', b'L', b'\xf7', b'T', b'a', b'.', b'\xb6', b'\xa3', b'Z', b'\xa3', b'\x0e', b'\xc1', b'l', b'\xd4', b'\x10', b'}', b'\xc4', b'Y', b'\xd1', b'\x18', b'1', b'\xfd', b'\x06', b'E', b'\x9a', b'\x1d', b'\xdc', b'\x9c', b'\x84', b'\x80', b'\x87', b'\xb1', b'L', b'\xb7', b'\x9e', b'`', b'l', b'b', b'\x0c', b'j', b'3', b'\x00', b'\x1f', b'\x83', b'Z', b'%', b'\xc1', b'\x83', b'l', b'\xe5', b'Z', b'\xea', b'q', b'\xae', b'\xf1')

L'exposant public est 0x010001 = 65537, et le module $n$ est obtenu à partir des 257 octets suivant la longueur 257 :

In [32]:
nn=ll[10:]
N = int(''.join(map(lambda x: "%02X"%x, map(ord,nn))),16)
print (N)
24197179286847076751267479433531096843909808556802245716741850737709306610630209095849920785452573764685331100203869311911185589435045631598880460398180094783356081371040731411086573360175212809003571417615130967550083085172889965589447863323037820202564159411800029468070675092209232569824831755420473655291845994055252634351126006832264373276055321558278890539694434772450398306253177305863877630248829723619454994737708243342606106336309152591177066470311180531219923044630357544050235563465629102942455163945967779489025449096558359347845799748312800495268065956440209963378476890619202259560003463746530818764529

C'est donc $N$ qu'il faut factoriser si on veut craquer la clef.

Le décodage de la clef privée est plus complexe. Elle est encodée avec le système DER de l'Union Internationale des Télécommunications. Il existe un module Python, pyasn1, qui peut décoder et réencoder.

Structure de la clef privée :

-----BEGIN RSA PRIVATE KEY-----
base64data
-----END RSA PRIVATE KEY-----

base64data DER structure:

RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p - 1)
exponent2 INTEGER, -- d mod (q - 1)
coefficient INTEGER -- q^-1 mod p
}
In [33]:
s = open('.ssh/id_rsa').read()
s
Out[33]:
'-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAv623jF8klGIdQO9vFaHL/aGoo3lTlBfbCs6lAVXwEejzV7Wj\nGKmOPYBTP2L5WEgysJrqxdbFTLbPQ1ktaHwG7tU6qjknBnsdm21awn4cVshHE+T5\nWJz2AZWie/9VEm3h1oHhRyAK3zs3pJxL47HnMKMYzoM0QwkQ/avT2t+T1/SA4Bhe\nc9qIwBuafOLGLSWi7x4kg1MX42f9HdmjSerykO4yYTScahphacEPlykiGf6EtTn3\nwjL5/3EBl43NeC4rZdYXUH/KZMuq+kz3VGEutqNaow7BbNQQfcRZ0Rgx/QZFmh3c\nnISAh7FMt55gbGIMajMAH4NaJcGDbOVa6nGu8QIDAQABAoIBAHgxLyJXWrGs4Fki\nio6O+UIeh4eSgaUgXFrnfzJaOAKTB1wdapsBX08TU6AwqNgB1b9GNSc/aFKVY1wA\n5GdbNmG21WV+FwmKU+Nta/b/azfDuEYyU2SMb/pIYS3NywOWYYHHyYJ3Bjo6gMa4\ntyGdIbIu41RDk5bhbYUTpPHfNm64LfTM/ZZUwfZGzPCv3VPDXLy2ws2NWWRcAyBH\nF6hMpS/ARTHKrssjz2MvvF0BmcvDRK1D//hmXbbK7LXJsjR63YCpFSAepXfrktyf\n8ILrnnatfmoO9DyEQQ56kceGALbyE/zy6frqw74MnTwoiLdiZxGeY4YIezWYL8SR\nHwwFOpECgYEA3aqnqAFbRLbzv5fU56gTO63EzOgCYN+2P0WrnH0BKK7AySxrtDgj\n4vhEoI5QpBZ42H9pza3WZNFZ1hCnIIH/EXR/ZIOX8N+TMus8d8d378JBlg36W8F8\ntEWjn6kW6kY8Yyjw7Hs+3jYqnDeIETeIQzzcm7pRViH8xw7NHbO3z+UCgYEA3V4A\nLMe9MOrVBT1YfFboQGuYGdO9qZRL4wH1oBe+9tbP3p5rLL7OsMid64A9aS4ebdGa\nCPlBUM3tr1NbHez9JbgpNLooCNLNV5iJn4iMt5yAyKpZ3DAwFgorTZ6iVdfAAUi1\no02Qq407va7tjpd/9ded/I9U2wfT4Jy1L86ceh0CgYBbNfiM6hn7GWkNAlXqCL/5\nQ5SCWEl6QTOFr45g8xMCAX50iSG8Y4lowI3EnyrRiimptCv+JTTeAUL9EZcjijpB\nnXU6D+f6hpTUU/VquBpC/uTr8M5+6Qv+RdWBQhuaxNHeX59bP49r8k/wPe1wYDBi\nsm14at9DGPMhmZaPTT8qfQKBgQC+saNk8AvCgAlRoi7/rb4VAJreZNEVrHJS8/Us\nHEidSx92nvGkchqLn8aqgKZmXRxJbi5LXK0vdrYyOpRbizPnsmWMznB+aVoLA5RK\noc7WvTMTqewPClPiKJB1JRqi6GC2unP+YWsm3VuBY5exJkFM/plSYAaxSGT1MQnE\nTS/u4QKBgHhygHlkyxpbcOGUAj/4puB1lLB6gzZfbDQVxi95VLBeR33+DUCi2FNR\ntmZoYJqsSO1G5DsEa7p4locjVjEMOGLmxm2vDlv6A6Rgip4l31OywMj/oMJVBpAM\nC3xh7uUSbfhMYRWWEcD+1X/7Nfmr2g7AswqLbzjDFU6o8POjV66m\n-----END RSA PRIVATE KEY-----\n'
In [34]:
s = s[s.index('\n')+1:]
s = s.replace('\n','')
s = s[:s.index('-')]
s=  decode(s.encode(), 'base64')
s
Out[34]:
b'0\x82\x04\xa3\x02\x01\x00\x02\x82\x01\x01\x00\xbf\xad\xb7\x8c_$\x94b\x1d@\xefo\x15\xa1\xcb\xfd\xa1\xa8\xa3yS\x94\x17\xdb\n\xce\xa5\x01U\xf0\x11\xe8\xf3W\xb5\xa3\x18\xa9\x8e=\x80S?b\xf9XH2\xb0\x9a\xea\xc5\xd6\xc5L\xb6\xcfCY-h|\x06\xee\xd5:\xaa9\'\x06{\x1d\x9bmZ\xc2~\x1cV\xc8G\x13\xe4\xf9X\x9c\xf6\x01\x95\xa2{\xffU\x12m\xe1\xd6\x81\xe1G \n\xdf;7\xa4\x9cK\xe3\xb1\xe70\xa3\x18\xce\x834C\t\x10\xfd\xab\xd3\xda\xdf\x93\xd7\xf4\x80\xe0\x18^s\xda\x88\xc0\x1b\x9a|\xe2\xc6-%\xa2\xef\x1e$\x83S\x17\xe3g\xfd\x1d\xd9\xa3I\xea\xf2\x90\xee2a4\x9cj\x1aai\xc1\x0f\x97)"\x19\xfe\x84\xb59\xf7\xc22\xf9\xffq\x01\x97\x8d\xcdx.+e\xd6\x17P\x7f\xcad\xcb\xaa\xfaL\xf7Ta.\xb6\xa3Z\xa3\x0e\xc1l\xd4\x10}\xc4Y\xd1\x181\xfd\x06E\x9a\x1d\xdc\x9c\x84\x80\x87\xb1L\xb7\x9e`lb\x0cj3\x00\x1f\x83Z%\xc1\x83l\xe5Z\xeaq\xae\xf1\x02\x03\x01\x00\x01\x02\x82\x01\x00x1/"WZ\xb1\xac\xe0Y"\x8a\x8e\x8e\xf9B\x1e\x87\x87\x92\x81\xa5 \\Z\xe7\x7f2Z8\x02\x93\x07\\\x1dj\x9b\x01_O\x13S\xa00\xa8\xd8\x01\xd5\xbfF5\'?hR\x95c\\\x00\xe4g[6a\xb6\xd5e~\x17\t\x8aS\xe3mk\xf6\xffk7\xc3\xb8F2Sd\x8co\xfaHa-\xcd\xcb\x03\x96a\x81\xc7\xc9\x82w\x06::\x80\xc6\xb8\xb7!\x9d!\xb2.\xe3TC\x93\x96\xe1m\x85\x13\xa4\xf1\xdf6n\xb8-\xf4\xcc\xfd\x96T\xc1\xf6F\xcc\xf0\xaf\xddS\xc3\\\xbc\xb6\xc2\xcd\x8dYd\\\x03 G\x17\xa8L\xa5/\xc0E1\xca\xae\xcb#\xcfc/\xbc]\x01\x99\xcb\xc3D\xadC\xff\xf8f]\xb6\xca\xec\xb5\xc9\xb24z\xdd\x80\xa9\x15 \x1e\xa5w\xeb\x92\xdc\x9f\xf0\x82\xeb\x9ev\xad~j\x0e\xf4<\x84A\x0ez\x91\xc7\x86\x00\xb6\xf2\x13\xfc\xf2\xe9\xfa\xea\xc3\xbe\x0c\x9d<(\x88\xb7bg\x11\x9ec\x86\x08{5\x98/\xc4\x91\x1f\x0c\x05:\x91\x02\x81\x81\x00\xdd\xaa\xa7\xa8\x01[D\xb6\xf3\xbf\x97\xd4\xe7\xa8\x13;\xad\xc4\xcc\xe8\x02`\xdf\xb6?E\xab\x9c}\x01(\xae\xc0\xc9,k\xb48#\xe2\xf8D\xa0\x8eP\xa4\x16x\xd8\x7fi\xcd\xad\xd6d\xd1Y\xd6\x10\xa7 \x81\xff\x11t\x7fd\x83\x97\xf0\xdf\x932\xeb<w\xc7w\xef\xc2A\x96\r\xfa[\xc1|\xb4E\xa3\x9f\xa9\x16\xeaF<c(\xf0\xec{>\xde6*\x9c7\x88\x117\x88C<\xdc\x9b\xbaQV!\xfc\xc7\x0e\xcd\x1d\xb3\xb7\xcf\xe5\x02\x81\x81\x00\xdd^\x00,\xc7\xbd0\xea\xd5\x05=X|V\xe8@k\x98\x19\xd3\xbd\xa9\x94K\xe3\x01\xf5\xa0\x17\xbe\xf6\xd6\xcf\xde\x9ek,\xbe\xce\xb0\xc8\x9d\xeb\x80=i.\x1em\xd1\x9a\x08\xf9AP\xcd\xed\xafS[\x1d\xec\xfd%\xb8)4\xba(\x08\xd2\xcdW\x98\x89\x9f\x88\x8c\xb7\x9c\x80\xc8\xaaY\xdc00\x16\n+M\x9e\xa2U\xd7\xc0\x01H\xb5\xa3M\x90\xab\x8d;\xbd\xae\xed\x8e\x97\x7f\xf5\xd7\x9d\xfc\x8fT\xdb\x07\xd3\xe0\x9c\xb5/\xce\x9cz\x1d\x02\x81\x80[5\xf8\x8c\xea\x19\xfb\x19i\r\x02U\xea\x08\xbf\xf9C\x94\x82XIzA3\x85\xaf\x8e`\xf3\x13\x02\x01~t\x89!\xbcc\x89h\xc0\x8d\xc4\x9f*\xd1\x8a)\xa9\xb4+\xfe%4\xde\x01B\xfd\x11\x97#\x8a:A\x9du:\x0f\xe7\xfa\x86\x94\xd4S\xf5j\xb8\x1aB\xfe\xe4\xeb\xf0\xce~\xe9\x0b\xfeE\xd5\x81B\x1b\x9a\xc4\xd1\xde_\x9f[?\x8fk\xf2O\xf0=\xedp`0b\xb2mxj\xdfC\x18\xf3!\x99\x96\x8fM?*}\x02\x81\x81\x00\xbe\xb1\xa3d\xf0\x0b\xc2\x80\tQ\xa2.\xff\xad\xbe\x15\x00\x9a\xded\xd1\x15\xacrR\xf3\xf5,\x1cH\x9dK\x1fv\x9e\xf1\xa4r\x1a\x8b\x9f\xc6\xaa\x80\xa6f]\x1cIn.K\\\xad/v\xb62:\x94[\x8b3\xe7\xb2e\x8c\xcep~iZ\x0b\x03\x94J\xa1\xce\xd6\xbd3\x13\xa9\xec\x0f\nS\xe2(\x90u%\x1a\xa2\xe8`\xb6\xbas\xfeak&\xdd[\x81c\x97\xb1&AL\xfe\x99R`\x06\xb1Hd\xf51\t\xc4M/\xee\xe1\x02\x81\x80xr\x80yd\xcb\x1a[p\xe1\x94\x02?\xf8\xa6\xe0u\x94\xb0z\x836_l4\x15\xc6/yT\xb0^G}\xfe\r@\xa2\xd8SQ\xb6fh`\x9a\xacH\xedF\xe4;\x04k\xbax\x96\x87#V1\x0c8b\xe6\xc6m\xaf\x0e[\xfa\x03\xa4`\x8a\x9e%\xdfS\xb2\xc0\xc8\xff\xa0\xc2U\x06\x90\x0c\x0b|a\xee\xe5\x12m\xf8La\x15\x96\x11\xc0\xfe\xd5\x7f\xfb5\xf9\xab\xda\x0e\xc0\xb3\n\x8bo8\xc3\x15N\xa8\xf0\xf3\xa3W\xae\xa6'

C'est à ce stade qu'il vaut mieux faire appel au décodeur de pyasn1 :

In [36]:
from pyasn1.codec.der import decoder
der=decoder.decode(s)
der
Out[36]:
(<SequenceOf value object, tagSet=<TagSet object, tags 0:32:16>, subtypeSpec=<ConstraintsIntersection object>, componentType=None, sizeSpec=<ConstraintsIntersection object>, payload [<Integer value object, tagSet <TagSet object, tags 0:0:2>, payload [0]>, <Integer value object, tagSet <TagSet object, tags 0:0:2>, payload [2419717928684707...3746530818764529]>, <Integer value object, tagSet <TagSet object, tags 0:0:2>, payload [65537]>, <Integer value object, tagSet <TagSet object, tags 0:0:2>, payload [1517285018833606...3859940738022033]>, <Integer value object, tagSet <TagSet object, tags 0:0:2>, payload [1556595946552543...8997631767465957]>, <Integer value object, tagSet <TagSet object, tags 0:0:2>, payload [1554493273635817...9837865199303197]>, <Integer value object, tagSet <TagSet object, tags 0:0:2>, payload [6405041868056585...7513910247238269]>, <Integer value object, tagSet <TagSet object, tags 0:0:2>, payload [1339098101170082...5539413120708321]>, <Integer value object, tagSet <TagSet object, tags 0:0:2>, payload [8458095138064153...3543979375505062]>]>,
 b'')
In [37]:
(version, n, e, d, p, q, e1, e2, c) = (int(x) for x in der[0])
In [38]:
print ("e = %d" % e)
print (hex(e))
print ("d = ",d)
print ("e*d mod (p-1)*(q-1) = ", e*d % ((p-1)*(q-1)))
print ("p*q-n = ", p*q-n)
print ("N-n = ",  N-n)
e = 65537
0x10001
d =  15172850188336063888999146548071477559248570771347304388810997696967574883864815948150701049455628406239890162242367050261534275261809363116346377161957535363565896576634860572479709663799080983047770990538074783884991140656116592701807527705879720176760824130306883302414886139346299227260195934952841369977350597441906754021159518664780005306118805594715343080488084374604059401335521866192949329558171222548434007661718013724887136902271039720597793760620351506991948830109271399986139475607937692536493513291795644092189491020024173088845020436085238851119034143510539946782308823105168030061480903859940738022033
e*d mod (p-1)*(q-1) =  1
p*q-n =  0
N-n =  0

Il existe un module rsa pour Python qui permet toutes ces manipulations de clefs.

In [42]:
import rsa
In [43]:
help(rsa)
Help on package rsa:

NAME
    rsa - RSA module

DESCRIPTION
    Module for calculating large primes, and RSA encryption, decryption, signing
    and verification. Includes generating public and private keys.
    
    WARNING: this implementation does not use compression of the cleartext input to
    prevent repetitions, or other common security improvements. Use with care.

PACKAGE CONTENTS
    _compat
    asn1
    cli
    common
    core
    key
    parallel
    pem
    pkcs1
    pkcs1_v2
    prime
    randnum
    transform
    util

CLASSES
    rsa.key.AbstractKey(builtins.object)
        rsa.key.PrivateKey
        rsa.key.PublicKey
    rsa.pkcs1.CryptoError(builtins.Exception)
        rsa.pkcs1.DecryptionError
        rsa.pkcs1.VerificationError
    
    class DecryptionError(CryptoError)
     |  Raised when decryption fails.
     |  
     |  Method resolution order:
     |      DecryptionError
     |      CryptoError
     |      builtins.Exception
     |      builtins.BaseException
     |      builtins.object
     |  
     |  Data descriptors inherited from CryptoError:
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from builtins.Exception:
     |  
     |  __init__(self, /, *args, **kwargs)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  ----------------------------------------------------------------------
     |  Static methods inherited from builtins.Exception:
     |  
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from builtins.BaseException:
     |  
     |  __delattr__(self, name, /)
     |      Implement delattr(self, name).
     |  
     |  __getattribute__(self, name, /)
     |      Return getattr(self, name).
     |  
     |  __reduce__(...)
     |      Helper for pickle.
     |  
     |  __repr__(self, /)
     |      Return repr(self).
     |  
     |  __setattr__(self, name, value, /)
     |      Implement setattr(self, name, value).
     |  
     |  __setstate__(...)
     |  
     |  __str__(self, /)
     |      Return str(self).
     |  
     |  with_traceback(...)
     |      Exception.with_traceback(tb) --
     |      set self.__traceback__ to tb and return self.
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from builtins.BaseException:
     |  
     |  __cause__
     |      exception cause
     |  
     |  __context__
     |      exception context
     |  
     |  __dict__
     |  
     |  __suppress_context__
     |  
     |  __traceback__
     |  
     |  args
    
    class PrivateKey(AbstractKey)
     |  PrivateKey(n: int, e: int, d: int, p: int, q: int) -> None
     |  
     |  Represents a private RSA key.
     |  
     |  This key is also known as the 'decryption key'. It contains the 'n', 'e',
     |  'd', 'p', 'q' and other values.
     |  
     |  Supports attributes as well as dictionary-like access. Attribute access is
     |  faster, though.
     |  
     |  >>> PrivateKey(3247, 65537, 833, 191, 17)
     |  PrivateKey(3247, 65537, 833, 191, 17)
     |  
     |  exp1, exp2 and coef will be calculated:
     |  
     |  >>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287)
     |  >>> pk.exp1
     |  55063
     |  >>> pk.exp2
     |  10095
     |  >>> pk.coef
     |  50797
     |  
     |  Method resolution order:
     |      PrivateKey
     |      AbstractKey
     |      builtins.object
     |  
     |  Methods defined here:
     |  
     |  __eq__(self, other: Any) -> bool
     |      Return self==value.
     |  
     |  __getitem__(self, key: str) -> int
     |  
     |  __getstate__(self) -> Tuple[int, int, int, int, int, int, int, int]
     |      Returns the key as tuple for pickling.
     |  
     |  __hash__(self) -> int
     |      Return hash(self).
     |  
     |  __init__(self, n: int, e: int, d: int, p: int, q: int) -> None
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  __ne__(self, other: Any) -> bool
     |      Return self!=value.
     |  
     |  __repr__(self) -> str
     |      Return repr(self).
     |  
     |  __setstate__(self, state: Tuple[int, int, int, int, int, int, int, int]) -> None
     |      Sets the key from tuple.
     |  
     |  blinded_decrypt(self, encrypted: int) -> int
     |      Decrypts the message using blinding to prevent side-channel attacks.
     |      
     |      :param encrypted: the encrypted message
     |      :type encrypted: int
     |      
     |      :returns: the decrypted message
     |      :rtype: int
     |  
     |  blinded_encrypt(self, message: int) -> int
     |      Encrypts the message using blinding to prevent side-channel attacks.
     |      
     |      :param message: the message to encrypt
     |      :type message: int
     |      
     |      :returns: the encrypted message
     |      :rtype: int
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  coef
     |  
     |  d
     |  
     |  e
     |  
     |  exp1
     |  
     |  exp2
     |  
     |  n
     |  
     |  p
     |  
     |  q
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from AbstractKey:
     |  
     |  blind(self, message: int, r: int) -> int
     |      Performs blinding on the message using random number 'r'.
     |      
     |      :param message: the message, as integer, to blind.
     |      :type message: int
     |      :param r: the random number to blind with.
     |      :type r: int
     |      :return: the blinded message.
     |      :rtype: int
     |      
     |      The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
     |      
     |      See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
     |  
     |  save_pkcs1(self, format: str = 'PEM') -> bytes
     |      Saves the key in PKCS#1 DER or PEM format.
     |      
     |      :param format: the format to save; 'PEM' or 'DER'
     |      :type format: str
     |      :returns: the DER- or PEM-encoded key.
     |      :rtype: bytes
     |  
     |  unblind(self, blinded: int, r: int) -> int
     |      Performs blinding on the message using random number 'r'.
     |      
     |      :param blinded: the blinded message, as integer, to unblind.
     |      :param r: the random number to unblind with.
     |      :return: the original message.
     |      
     |      The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
     |      
     |      See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
     |  
     |  ----------------------------------------------------------------------
     |  Class methods inherited from AbstractKey:
     |  
     |  load_pkcs1(keyfile: bytes, format: str = 'PEM') -> 'AbstractKey' from builtins.type
     |      Loads a key in PKCS#1 DER or PEM format.
     |      
     |      :param keyfile: contents of a DER- or PEM-encoded file that contains
     |          the key.
     |      :type keyfile: bytes
     |      :param format: the format of the file to load; 'PEM' or 'DER'
     |      :type format: str
     |      
     |      :return: the loaded key
     |      :rtype: AbstractKey
    
    class PublicKey(AbstractKey)
     |  PublicKey(n: int, e: int) -> None
     |  
     |  Represents a public RSA key.
     |  
     |  This key is also known as the 'encryption key'. It contains the 'n' and 'e'
     |  values.
     |  
     |  Supports attributes as well as dictionary-like access. Attribute access is
     |  faster, though.
     |  
     |  >>> PublicKey(5, 3)
     |  PublicKey(5, 3)
     |  
     |  >>> key = PublicKey(5, 3)
     |  >>> key.n
     |  5
     |  >>> key['n']
     |  5
     |  >>> key.e
     |  3
     |  >>> key['e']
     |  3
     |  
     |  Method resolution order:
     |      PublicKey
     |      AbstractKey
     |      builtins.object
     |  
     |  Methods defined here:
     |  
     |  __eq__(self, other: Any) -> bool
     |      Return self==value.
     |  
     |  __getitem__(self, key: str) -> int
     |  
     |  __getstate__(self) -> Tuple[int, int]
     |      Returns the key as tuple for pickling.
     |  
     |  __hash__(self) -> int
     |      Return hash(self).
     |  
     |  __ne__(self, other: Any) -> bool
     |      Return self!=value.
     |  
     |  __repr__(self) -> str
     |      Return repr(self).
     |  
     |  __setstate__(self, state: Tuple[int, int]) -> None
     |      Sets the key from tuple.
     |  
     |  ----------------------------------------------------------------------
     |  Class methods defined here:
     |  
     |  load_pkcs1_openssl_der(keyfile: bytes) -> 'PublicKey' from builtins.type
     |      Loads a PKCS#1 DER-encoded public key file from OpenSSL.
     |      
     |      :param keyfile: contents of a DER-encoded file that contains the public
     |          key, from OpenSSL.
     |      :return: a PublicKey object
     |  
     |  load_pkcs1_openssl_pem(keyfile: bytes) -> 'PublicKey' from builtins.type
     |      Loads a PKCS#1.5 PEM-encoded public key file from OpenSSL.
     |      
     |      These files can be recognised in that they start with BEGIN PUBLIC KEY
     |      rather than BEGIN RSA PUBLIC KEY.
     |      
     |      The contents of the file before the "-----BEGIN PUBLIC KEY-----" and
     |      after the "-----END PUBLIC KEY-----" lines is ignored.
     |      
     |      :param keyfile: contents of a PEM-encoded file that contains the public
     |          key, from OpenSSL.
     |      :type keyfile: bytes
     |      :return: a PublicKey object
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  e
     |  
     |  n
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from AbstractKey:
     |  
     |  __init__(self, n: int, e: int) -> None
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  blind(self, message: int, r: int) -> int
     |      Performs blinding on the message using random number 'r'.
     |      
     |      :param message: the message, as integer, to blind.
     |      :type message: int
     |      :param r: the random number to blind with.
     |      :type r: int
     |      :return: the blinded message.
     |      :rtype: int
     |      
     |      The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
     |      
     |      See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
     |  
     |  save_pkcs1(self, format: str = 'PEM') -> bytes
     |      Saves the key in PKCS#1 DER or PEM format.
     |      
     |      :param format: the format to save; 'PEM' or 'DER'
     |      :type format: str
     |      :returns: the DER- or PEM-encoded key.
     |      :rtype: bytes
     |  
     |  unblind(self, blinded: int, r: int) -> int
     |      Performs blinding on the message using random number 'r'.
     |      
     |      :param blinded: the blinded message, as integer, to unblind.
     |      :param r: the random number to unblind with.
     |      :return: the original message.
     |      
     |      The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
     |      
     |      See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
     |  
     |  ----------------------------------------------------------------------
     |  Class methods inherited from AbstractKey:
     |  
     |  load_pkcs1(keyfile: bytes, format: str = 'PEM') -> 'AbstractKey' from builtins.type
     |      Loads a key in PKCS#1 DER or PEM format.
     |      
     |      :param keyfile: contents of a DER- or PEM-encoded file that contains
     |          the key.
     |      :type keyfile: bytes
     |      :param format: the format of the file to load; 'PEM' or 'DER'
     |      :type format: str
     |      
     |      :return: the loaded key
     |      :rtype: AbstractKey
    
    class VerificationError(CryptoError)
     |  Raised when verification fails.
     |  
     |  Method resolution order:
     |      VerificationError
     |      CryptoError
     |      builtins.Exception
     |      builtins.BaseException
     |      builtins.object
     |  
     |  Data descriptors inherited from CryptoError:
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from builtins.Exception:
     |  
     |  __init__(self, /, *args, **kwargs)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  ----------------------------------------------------------------------
     |  Static methods inherited from builtins.Exception:
     |  
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from builtins.BaseException:
     |  
     |  __delattr__(self, name, /)
     |      Implement delattr(self, name).
     |  
     |  __getattribute__(self, name, /)
     |      Return getattr(self, name).
     |  
     |  __reduce__(...)
     |      Helper for pickle.
     |  
     |  __repr__(self, /)
     |      Return repr(self).
     |  
     |  __setattr__(self, name, value, /)
     |      Implement setattr(self, name, value).
     |  
     |  __setstate__(...)
     |  
     |  __str__(self, /)
     |      Return str(self).
     |  
     |  with_traceback(...)
     |      Exception.with_traceback(tb) --
     |      set self.__traceback__ to tb and return self.
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from builtins.BaseException:
     |  
     |  __cause__
     |      exception cause
     |  
     |  __context__
     |      exception context
     |  
     |  __dict__
     |  
     |  __suppress_context__
     |  
     |  __traceback__
     |  
     |  args

FUNCTIONS
    compute_hash(message: Union[bytes, BinaryIO], method_name: str) -> bytes
        Returns the message digest.
        
        :param message: the signed message. Can be an 8-bit string or a file-like
            object. If ``message`` has a ``read()`` method, it is assumed to be a
            file-like object.
        :param method_name: the hash method, must be a key of
            :py:const:`HASH_METHODS`.
    
    decrypt(crypto: bytes, priv_key: rsa.key.PrivateKey) -> bytes
        Decrypts the given message using PKCS#1 v1.5
        
        The decryption is considered 'failed' when the resulting cleartext doesn't
        start with the bytes 00 02, or when the 00 byte between the padding and
        the message cannot be found.
        
        :param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
        :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
        :raise DecryptionError: when the decryption fails. No details are given as
            to why the code thinks the decryption fails, as this would leak
            information about the private key.
        
        
        >>> import rsa
        >>> (pub_key, priv_key) = rsa.newkeys(256)
        
        It works with strings:
        
        >>> crypto = encrypt(b'hello', pub_key)
        >>> decrypt(crypto, priv_key)
        b'hello'
        
        And with binary data:
        
        >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
        >>> decrypt(crypto, priv_key)
        b'\x00\x00\x00\x00\x01'
        
        Altering the encrypted information will *likely* cause a
        :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
        :py:func:`rsa.sign`.
        
        
        .. warning::
        
            Never display the stack trace of a
            :py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
            code the exception occurred, and thus leaks information about the key.
            It's only a tiny bit of information, but every bit makes cracking the
            keys easier.
        
        >>> crypto = encrypt(b'hello', pub_key)
        >>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
        >>> decrypt(crypto, priv_key)
        Traceback (most recent call last):
        ...
        rsa.pkcs1.DecryptionError: Decryption failed
    
    encrypt(message: bytes, pub_key: rsa.key.PublicKey) -> bytes
        Encrypts the given message using PKCS#1 v1.5
        
        :param message: the message to encrypt. Must be a byte string no longer than
            ``k-11`` bytes, where ``k`` is the number of bytes needed to encode
            the ``n`` component of the public key.
        :param pub_key: the :py:class:`rsa.PublicKey` to encrypt with.
        :raise OverflowError: when the message is too large to fit in the padded
            block.
        
        >>> from rsa import key, common
        >>> (pub_key, priv_key) = key.newkeys(256)
        >>> message = b'hello'
        >>> crypto = encrypt(message, pub_key)
        
        The crypto text should be just as long as the public key 'n' component:
        
        >>> len(crypto) == common.byte_size(pub_key.n)
        True
    
    find_signature_hash(signature: bytes, pub_key: rsa.key.PublicKey) -> str
        Returns the hash name detected from the signature.
        
        If you also want to verify the message, use :py:func:`rsa.verify()` instead.
        It also returns the name of the used hash.
        
        :param signature: the signature block, as created with :py:func:`rsa.sign`.
        :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
        :returns: the name of the used hash.
    
    newkeys(nbits: int, accurate: bool = True, poolsize: int = 1, exponent: int = 65537) -> Tuple[rsa.key.PublicKey, rsa.key.PrivateKey]
        Generates public and private keys, and returns them as (pub, priv).
        
        The public key is also known as the 'encryption key', and is a
        :py:class:`rsa.PublicKey` object. The private key is also known as the
        'decryption key' and is a :py:class:`rsa.PrivateKey` object.
        
        :param nbits: the number of bits required to store ``n = p*q``.
        :param accurate: when True, ``n`` will have exactly the number of bits you
            asked for. However, this makes key generation much slower. When False,
            `n`` may have slightly less bits.
        :param poolsize: the number of processes to use to generate the prime
            numbers. If set to a number > 1, a parallel algorithm will be used.
            This requires Python 2.6 or newer.
        :param exponent: the exponent for the key; only change this if you know
            what you're doing, as the exponent influences how difficult your
            private key can be cracked. A very common choice for e is 65537.
        :type exponent: int
        
        :returns: a tuple (:py:class:`rsa.PublicKey`, :py:class:`rsa.PrivateKey`)
        
        The ``poolsize`` parameter was added in *Python-RSA 3.1* and requires
        Python 2.6 or newer.
    
    sign(message: bytes, priv_key: rsa.key.PrivateKey, hash_method: str) -> bytes
        Signs the message with the private key.
        
        Hashes the message, then signs the hash with the given key. This is known
        as a "detached signature", because the message itself isn't altered.
        
        :param message: the message to sign. Can be an 8-bit string or a file-like
            object. If ``message`` has a ``read()`` method, it is assumed to be a
            file-like object.
        :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
        :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
            'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
        :return: a message signature block.
        :raise OverflowError: if the private key is too small to contain the
            requested hash.
    
    sign_hash(hash_value: bytes, priv_key: rsa.key.PrivateKey, hash_method: str) -> bytes
        Signs a precomputed hash with the private key.
        
        Hashes the message, then signs the hash with the given key. This is known
        as a "detached signature", because the message itself isn't altered.
        
        :param hash_value: A precomputed hash to sign (ignores message).
        :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
        :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
            'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
        :return: a message signature block.
        :raise OverflowError: if the private key is too small to contain the
            requested hash.
    
    verify(message: bytes, signature: bytes, pub_key: rsa.key.PublicKey) -> str
        Verifies that the signature matches the message.
        
        The hash method is detected automatically from the signature.
        
        :param message: the signed message. Can be an 8-bit string or a file-like
            object. If ``message`` has a ``read()`` method, it is assumed to be a
            file-like object.
        :param signature: the signature block, as created with :py:func:`rsa.sign`.
        :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
        :raise VerificationError: when the signature doesn't match the message.
        :returns: the name of the used hash.

DATA
    __all__ = ['newkeys', 'encrypt', 'decrypt', 'sign', 'verify', 'PublicK...

VERSION
    4.6

DATE
    2020-06-12

AUTHOR
    Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly

FILE
    /home/jyt/anaconda3/lib/python3.7/site-packages/rsa/__init__.py


Signature numérique RSA (DSA, Digital Signature Algorithm)

La cryptographie à clef publique permet de signer numériquement un document. Le principe est le suivant. On calcule un hachage $h= H(x)$ du document $x$, et on le chiffre avec sa clef privée. La signature est $(h,s) = E(h,K_S))$ Alors, tout le monde peut déchiffrer $s$ avec la clef publique, recalculer le hachage et comparer les résultats.

Bien entendu, il y a des précautions à prendre dans le choix des paramètres, du bourrage, etc. Les protocoles autorisés sont décrits dans ce document.

Le module rsa gère les signatures de façon transparente.

In [44]:
(KP,KS) = rsa.newkeys(512)
print (KP)
print (KS)
message = 'La clef du coffre est dans le pot de fleurs'
signature = rsa.sign(message.encode(), KS, 'SHA-1')
print (message)
signature
PublicKey(7496487422217807100249186541091547368226043268417179401207860425209820899065605033306276356511577186576905015081319687471223001768735907148792531143439169, 65537)
PrivateKey(7496487422217807100249186541091547368226043268417179401207860425209820899065605033306276356511577186576905015081319687471223001768735907148792531143439169, 65537, 1950159666469191346142612285259773732103785826071448061571216448561915199123581901544222566884669543219217288075688163657958376550213443970968184088130561, 6053569893261905312732947326825457214986020551747364100541460524984696150250963553, 1238358118333114706137872610429255662109475762980378879288541106062056673)
La clef du coffre est dans le pot de fleurs
Out[44]:
b'PI^\xa5\x97\x92?\xb3c\xa9\xdfg\x1d\xd5\x13$]\xf9g\xea\xcb\xacC\xe2\x1b\xa1\x84\xec\x02q\xc3*"j\xb6\xab\xe1\x04\xdb&\xcd#\xba\x96D\x15\\(\xd3\xfa\xbef\xd4\xc3\x84\xcf~@\x84\xa3\x14\xb6\t#'
In [45]:
rsa.verify(message.encode(), signature, KP)
Out[45]:
'SHA-1'
In [46]:
help(rsa.verify)
Help on function verify in module rsa.pkcs1:

verify(message: bytes, signature: bytes, pub_key: rsa.key.PublicKey) -> str
    Verifies that the signature matches the message.
    
    The hash method is detected automatically from the signature.
    
    :param message: the signed message. Can be an 8-bit string or a file-like
        object. If ``message`` has a ``read()`` method, it is assumed to be a
        file-like object.
    :param signature: the signature block, as created with :py:func:`rsa.sign`.
    :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
    :raise VerificationError: when the signature doesn't match the message.
    :returns: the name of the used hash.

Attaques contre le RSA

En pratique, la mise en oeuvre du RSA nécessite certaines précautions, décrites dans les RFC et autres standards officiels.

L'attaque la plus évidente est la factorisation du module. Ce qui est impossible aujourd'hui sera peut-être possible demain, avec la découverte de nouveaux algorithmes et l'augmentation de la puissance des machines.

Factorisation du module

Ce risque est bien illustré par l'affaire des cartes bancaires. Jusqu'à une date récente, un paiement par carte bancaire se déroulait ainsi. La mémoire de la carte est divisée en deux zones, une accessible avec n'importe quel lecteur, et une autre (zone secrète) qui n'est accessible qu'au processeur de la carte, et illisible autrement. La zone publique contient une valeur d'authentification, qui est obtenue en chiffrant des données publiques (comme le numéro de la carte et sa date d'expiration) avec une clé RSA secrète. Les terminaux de paiement et les distributeurs de billets connaissent la clé publique, et l'utilisent pour vérifier la valeur d'authentification, qui est précisément une signature numérique RSA, sans hachage vu le petit nombre de données.

Si la signature est correcte, la carte est considérée comme authentique, et le code est demandé au client. Le terminal envoie le code à la carte qui le compare avec celui contenu dans sa zone secrète, et répond « oui » ou «non ». Si la réponse est « oui », le paiement est accepté.

Seulement voilà. Les cartes émises vers 1998 utilisaient la clé publique

$$e=3, \quad n = 2135987035920910082395022704999628797051095341826417406442524165008583957746445088405009430865999$$

Cette clé était trop courte, les algorithmes ayant progressé, elle a pu être factorisée. A partir de ce moment, il devenait possible de fabriquer de fausses cartes, en chiffrant des données d'authentification fantaisistes sur une carte programmable, et en programmant la carte pour qu'elle réponde « oui » (les deux octets 0x90, 0x00 dans son langage) quelle que soit la question posée (les fameuses yescards).

L'histoire était racontée sur ce site (qui n'existe plus, mais archive.org peut nous le retrouver).

Le programme msieve répond en 18 secondes sur une station de travail moyennement récente.

Les clefs faibles

S'il est en général impossible de retrouver $p$ et $q$ à partir de $n = pq$ pour deux grands nombres premiers $p$ et $q$, on connaît de nombreux cas particuliers dans lesquels il est possible de craquer la clé RSA. On trouvera ici un exposé récent de l'état de l'art dans ce domaine. Une première chose à réaliser, c'est que si on connaît $\varphi(n)$, alors on peut factoriser $n$. En effet, $\varphi(n) = (p-1)(q-1)$, donc $p$ et $q$ sont les deux racines de l'équation du second degré $x^2-(n-\varphi(n)+1)x+n = 0$.

Bien sûr, $\varphi(n)$ n'est pas public, mais dans certaines circonstances, on peut le retrouver. C'est le cas si $3d<n^{1/4}$, $q<p<2q$ (attaque de Wiener). Il existe de nombreuses variantes de cette attaque, et d'autres types de clés faibles. Le programme wiener.py montre comment la mettre en oeuvre. Il utilise quelques fonctions de ent.py, une petite collection d'exemples écrite par W. Stein (maître d'oeuvre du projet SAGE) pour illustrer son cours de cryptographie. Ce fichier sera donné au prochain TD.

Les erreurs de protocole

Même si la clé est résistante, un mauvais usage du système peut être catastrophique.

Par exemple, supposons que $B$ et $B'$ aient le même module $n$ et des exposants publics $e$ et $e'$, avec $pgcd(e,e')=1$.

A envoie un même message $x$ aux deux, donc $y=x^e \mod n$ à $B$ et $y' = x^{e'} \mod n$ à $B'$. Si O intercepte $y$ et $y'$, il peut retrouver x sans factoriser la clé. (Problème du module commun).

Si $B$, $B'$ et $B''$ ont pour modules $n$, $n'$, et $n''$ (deux à deux premiers entre eux) et un même exposant public $e$ (c'est fréquent, beaucoup de systèmes utilisent $e=3$ par exemple), A envoie $x^3 \mod n, n'$ et $n''$ aux trois. O intercepte la communication et peut retrouver x sans factoriser la clé.

Autres attaques

Il y en a beaucoup, voir ici pour plus d'information.

In [103]:
 
In [ ]: