summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLegrandin <gooksankoo@hoiptorrow.mailexpire.com>2011-10-03 23:33:11 +0200
committerLegrandin <gooksankoo@hoiptorrow.mailexpire.com>2011-10-03 23:33:11 +0200
commit674b80db746ab4898cfb6a5fe44a642c7cb4c946 (patch)
treeef486fcbdaeadb655b7ad551adb0d1a327e917fb
parent9cb1a2d35d916180dee8351fe6f2ddf4f6dba72d (diff)
downloadpycrypto-674b80db746ab4898cfb6a5fe44a642c7cb4c946.tar.gz
Added support for pass phrase and DES/3DES encrypted PEM keys, for both import and export.
-rw-r--r--lib/Crypto/PublicKey/RSA.py71
-rw-r--r--lib/Crypto/SelfTest/PublicKey/test_importKey.py53
2 files changed, 117 insertions, 7 deletions
diff --git a/lib/Crypto/PublicKey/RSA.py b/lib/Crypto/PublicKey/RSA.py
index 908ec68..e8b4c0a 100644
--- a/lib/Crypto/PublicKey/RSA.py
+++ b/lib/Crypto/PublicKey/RSA.py
@@ -166,16 +166,19 @@ class _RSAobj(pubkey.pubkey):
attrs.append("private")
return "<%s @0x%x %s>" % (self.__class__.__name__, id(self), ",".join(attrs))
- def exportKey(self, format='PEM'):
+ def exportKey(self, format='PEM', passphrase=None):
"""Export this RSA key.
:Parameter format: The encoding to use to wrap the key.
- - *'DER'* for PKCS#1
+ - *'DER'* for PKCS#1. Always unencrypted.
- *'OpenSSH'* for OpenSSH public keys (no private key)
- - *'PEM'* for RFC1421
+ - *'PEM'* for RFC1421/3. Unencrypted (default) or encrypted.
:Type format: string
+ :Parameter passphrase: In case of PEM, the pass phrase to derive the encryption key from.
+ :Type passphrase: string
+
:Return: A string with the encoded public or private half.
:Raise ValueError:
When the format is unknown.
@@ -209,7 +212,25 @@ class _RSAobj(pubkey.pubkey):
return der.encode()
if format=='PEM':
pem = "-----BEGIN %s KEY-----\n" % keyType
+ keyobj = None
+ if passphrase and keyType=='RSA PRIVATE':
+ # We only support 3DES for encryption
+ import Crypto.Hash.MD5
+ from Crypto.Cipher import DES3
+ from Crypto.Protocol.KDF import PBKDF1
+ salt = self._randfunc(8)
+ key = PBKDF1(passphrase, salt, 16, 1, Crypto.Hash.MD5)
+ key += PBKDF1(key+passphrase, salt, 8, 1, Crypto.Hash.MD5)
+ keyobj = DES3.new(key, Crypto.Cipher.DES3.MODE_CBC, salt)
+ pem += 'Proc-Type: 4,ENCRYPTED\n'
+ pem += 'DEK-Info: DES-EDE3-CBC,' + binascii.b2a_hex(salt).upper() + '\n\n'
+
binaryKey = der.encode()
+ if keyobj:
+ # Add PKCS#7-like padding
+ padding = keyobj.block_size-len(binaryKey)%keyobj.block_size
+ binaryKey = keyobj.encrypt(binaryKey+chr(padding)*padding)
+
# Each BASE64 line can take up to 64 characters (=48 bytes of data)
chunks = [ binascii.b2a_base64(binaryKey[i:i+48]) for i in range(0, len(binaryKey), 48) ]
pem += ''.join(chunks)
@@ -380,23 +401,59 @@ class RSAImplementation(object):
return self.construct(der[:])
raise ValueError("RSA key format is not supported")
- def importKey(self, externKey):
+ def importKey(self, externKey, passphrase=None):
"""Import an RSA key (public or private half), encoded in standard form.
:Parameter externKey:
The RSA key to import, encoded as a string.
- The key can be in DER (PKCS#1), OpenSSH or in unencrypted PEM format (RFC1421).
+ The key can be in DER (PKCS#1), OpenSSH or in PEM format (RFC1421/3).
+ In case of PEM, the key can be encrypted with DES or 3TDES according to a certain pass phrase.
+ Only OpenSSL-compatible pass phrases are supported.
:Type externKey: string
- :Raise ValueError/IndexError:
- When the given key cannot be parsed.
+ :Parameter passphrase:
+ In case of an encrypted PEM key, this is the pass phrase from which the encryption key is derived.
+ :Type passphrase: string
+
+ :Raise ValueError/IndexError/TypeError:
+ When the given key cannot be parsed (possibly because the pass phrase is wrong).
"""
if externKey.startswith('-----'):
# This is probably a PEM encoded key
lines = externKey.replace(" ",'').split()
+ keyobj = None
+
+ # The encrypted PEM format
+ if lines[1].startswith('Proc-Type:4,ENCRYPTED'):
+ DEK = lines[2].split(':')
+ if len(DEK)!=2 or DEK[0]!='DEK-Info' or not passphrase:
+ raise ValueError("PEM encryption format not supported.")
+ algo, salt = DEK[1].split(',')
+ salt = binascii.a2b_hex(salt)
+ import Crypto.Hash.MD5
+ from Crypto.Cipher import DES, DES3
+ from Crypto.Protocol.KDF import PBKDF1
+ if algo=="DES-CBC":
+ # This is EVP_BytesToKey in OpenSSL
+ key = PBKDF1(passphrase, salt, 8, 1, Crypto.Hash.MD5)
+ keyobj = DES.new(key, Crypto.Cipher.DES.MODE_CBC, salt)
+ elif algo=="DES-EDE3-CBC":
+ # Note that EVP_BytesToKey is note exactly the same as PBKDF1
+ key = PBKDF1(passphrase, salt, 16, 1, Crypto.Hash.MD5)
+ key += PBKDF1(key+passphrase, salt, 8, 1, Crypto.Hash.MD5)
+ keyobj = DES3.new(key, Crypto.Cipher.DES3.MODE_CBC, salt)
+ else:
+ raise ValueError("Unsupport PEM encryption algorithm.")
+ lines = lines[2:]
+
der = binascii.a2b_base64(''.join(lines[1:-1]))
+ if keyobj:
+ der = keyobj.decrypt(der)
+ padding = ord(der[-1])
+ der = der[:-padding]
return self._importKeyDER(der)
+
if externKey.startswith('ssh-rsa '):
# This is probably an OpenSSH key
keystring = binascii.a2b_base64(externKey.split(' ')[1])
diff --git a/lib/Crypto/SelfTest/PublicKey/test_importKey.py b/lib/Crypto/SelfTest/PublicKey/test_importKey.py
index 018754d..fe4fee2 100644
--- a/lib/Crypto/SelfTest/PublicKey/test_importKey.py
+++ b/lib/Crypto/SelfTest/PublicKey/test_importKey.py
@@ -42,6 +42,40 @@ JACAr3sJQJGxIQIgarRp+m1WSKV1MciwMaTOnbU7wxFs9DP1pva76lYBzgUCIQC9
n0CnZCJ6IZYqSt0H5N7+Q+2Ro64nuwV/OSQfM6sBwQ==
-----END RSA PRIVATE KEY-----'''
+ # The same RSA private key as in rsaKeyPEM, but now encrypted
+ rsaKeyEncryptedPEM=(
+
+ # With DES and passphrase 'test'
+ ('test', '''-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-CBC,AF8F9A40BD2FA2FC
+
+Ckl9ex1kaVEWhYC2QBmfaF+YPiR4NFkRXA7nj3dcnuFEzBnY5XULupqQpQI3qbfA
+u8GYS7+b3toWWiHZivHbAAUBPDIZG9hKDyB9Sq2VMARGsX1yW1zhNvZLIiVJzUHs
+C6NxQ1IJWOXzTew/xM2I26kPwHIvadq+/VaT8gLQdjdH0jOiVNaevjWnLgrn1mLP
+BCNRMdcexozWtAFNNqSzfW58MJL2OdMi21ED184EFytIc1BlB+FZiGZduwKGuaKy
+9bMbdb/1PSvsSzPsqW7KSSrTw6MgJAFJg6lzIYvR5F4poTVBxwBX3+EyEmShiaNY
+IRX3TgQI0IjrVuLmvlZKbGWP18FXj7I7k9tSsNOOzllTTdq3ny5vgM3A+ynfAaxp
+dysKznQ6P+IoqML1WxAID4aGRMWka+uArOJ148Rbj9s=
+-----END RSA PRIVATE KEY-----''',
+ "\xAF\x8F\x9A\x40\xBD\x2F\xA2\xFC"),
+
+ # With Triple-DES and passphrase 'rocking'
+ ('rocking', '''-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,C05D6C07F7FC02F6
+
+w4lwQrXaVoTTJ0GgwY566htTA2/t1YlimhxkxYt9AEeCcidS5M0Wq9ClPiPz9O7F
+m6K5QpM1rxo1RUE/ZyI85gglRNPdNwkeTOqit+kum7nN73AToX17+irVmOA4Z9E+
+4O07t91GxGMcjUSIFk0ucwEU4jgxRvYscbvOMvNbuZszGdVNzBTVddnShKCsy9i7
+nJbPlXeEKYi/OkRgO4PtfqqWQu5GIEFVUf9ev1QV7AvC+kyWTR1wWYnHX265jU5c
+sopxQQtP8XEHIJEdd5/p1oieRcWTCNyY8EkslxDSsrf0OtZp6mZH9N+KU47cgQtt
+9qGORmlWnsIoFFKcDohbtOaWBTKhkj5h6OkLjFjfU/sBeV1c+7wDT3dAy5tawXjG
+YSxC7qDQIT/RECvV3+oQKEcmpEujn45wAnkTi12BH30=
+-----END RSA PRIVATE KEY-----''',
+ "\xC0\x5D\x6C\x07\xF7\xFC\x02\xF6"),
+ )
+
rsaPublicKeyPEM = '''-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL8eJ5AKoIsjURpcEoGubZMxLD7+kT+T
Lr7UkvEtFrRhDDKMtuIIq19FrL4pUIMymPMSLBn3hJLe30Dw48GQM4UCAwEAAQ==
@@ -129,6 +163,16 @@ Lr7UkvEtFrRhDDKMtuIIq19FrL4pUIMymPMSLBn3hJLe30Dw48GQM4UCAwEAAQ==
self.assertEqual(key.n, self.n)
self.assertEqual(key.e, self.e)
+ def testImportKey8(self):
+ for t in self.rsaKeyEncryptedPEM:
+ key = self.rsa.importKey(t[1], t[0])
+ self.failUnless(key.has_private())
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+ self.assertEqual(key.d, self.d)
+ self.assertEqual(key.p, self.p)
+ self.assertEqual(key.q, self.q)
+
###
def testExportKey1(self):
key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv])
@@ -157,6 +201,15 @@ Lr7UkvEtFrRhDDKMtuIIq19FrL4pUIMymPMSLBn3hJLe30Dw48GQM4UCAwEAAQ==
self.assertEqual(openssh_1[0], openssh_2[0])
self.assertEqual(openssh_1[1], openssh_2[1])
+ def testExportKey4(self):
+ key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv])
+ # Tuple with index #1 is encrypted with 3DES
+ t = self.rsaKeyEncryptedPEM[1]
+ # Force the salt being used when exporting
+ key._randfunc = lambda N: (t[2]*divmod(N+len(t[2]),len(t[2]))[0])[:N]
+ pemKey = key.exportKey("PEM", t[0])
+ self.assertEqual(pemKey, t[1])
+
class ImportKeyTestsSlow(ImportKeyTests):
def setUp(self):
self.rsa = RSA.RSAImplementation(use_fast_math=0)