From 966821aa1ab232e52c71d1ad7a7745bc9c6a08fb Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 19 Jan 2014 21:22:08 -0600 Subject: add cipher support to the commoncrypto backend --- .../hazmat/backends/commoncrypto/backend.py | 164 ++++++++++++++++++++- tests/hazmat/backends/test_commoncrypto.py | 31 ++++ 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 tests/hazmat/backends/test_commoncrypto.py diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 58e57efb..bdaff301 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -18,10 +18,16 @@ from collections import namedtuple from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends.interfaces import ( - HashBackend, HMACBackend, + HashBackend, HMACBackend, CipherBackend ) from cryptography.hazmat.bindings.commoncrypto.binding import Binding from cryptography.hazmat.primitives import interfaces +from cryptography.hazmat.primitives.ciphers.algorithms import ( + AES, Blowfish, TripleDES, ARC4 +) +from cryptography.hazmat.primitives.ciphers.modes import ( + CBC, CTR, ECB, OFB, CFB +) HashMethods = namedtuple( @@ -29,6 +35,7 @@ HashMethods = namedtuple( ) +@utils.register_interface(CipherBackend) @utils.register_interface(HashBackend) @utils.register_interface(HMACBackend) class Backend(object): @@ -42,6 +49,8 @@ class Backend(object): self._ffi = self._binding.ffi self._lib = self._binding.lib + self._cipher_registry = {} + self._register_default_ciphers() self._hash_mapping = { "md5": HashMethods( "CC_MD5_CTX *", self._lib.CC_MD5_Init, @@ -98,6 +107,159 @@ class Backend(object): def create_hmac_ctx(self, key, algorithm): return _HMACContext(self, key, algorithm) + def cipher_supported(self, cipher, mode): + try: + self._cipher_registry[type(cipher), type(mode)] + except KeyError: + return False + return True + + def create_symmetric_encryption_ctx(self, cipher, mode): + return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) + + def create_symmetric_decryption_ctx(self, cipher, mode): + return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + + def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): + if (cipher_cls, mode_cls) in self._cipher_registry: + raise ValueError("Duplicate registration for: {0} {1}".format( + cipher_cls, mode_cls) + ) + self._cipher_registry[cipher_cls, mode_cls] = adapter + + def _register_default_ciphers(self): + for mode_cls in [CBC, ECB, CFB, OFB, CTR]: + self.register_cipher_adapter( + AES, + mode_cls, + _GetCipherModeEnum() + ) + for mode_cls in [CBC, CFB, OFB]: + self.register_cipher_adapter( + TripleDES, + mode_cls, + _GetCipherModeEnum() + ) + for mode_cls in [CBC, CFB, OFB, ECB]: + self.register_cipher_adapter( + Blowfish, + mode_cls, + _GetCipherModeEnum() + ) + self.register_cipher_adapter( + ARC4, + type(None), + _GetCipherModeEnum() + ) + + +class _GetCipherModeEnum(object): + def __call__(self, backend, cipher, mode): + cipher_enum = { + AES: backend._lib.kCCAlgorithmAES128, + TripleDES: backend._lib.kCCAlgorithm3DES, + Blowfish: backend._lib.kCCAlgorithmBlowfish, + ARC4: backend._lib.kCCAlgorithmRC4, + }[type(cipher)] + + mode_enum = { + ECB: backend._lib.kCCModeECB, + CBC: backend._lib.kCCModeCBC, + CTR: backend._lib.kCCModeCTR, + CFB: backend._lib.kCCModeCFB, + OFB: backend._lib.kCCModeOFB, + type(None): backend._lib.kCCModeRC4, + }[type(mode)] + + return (cipher_enum, mode_enum) + + +@utils.register_interface(interfaces.CipherContext) +class _CipherContext(object): + _ENCRYPT = 0 # kCCEncrypt + _DECRYPT = 1 # kCCDecrypt + + def __init__(self, backend, cipher, mode, operation): + self._backend = backend + self._cipher = cipher + self._mode = mode + self._operation = operation + # bytes_processed is needed to work around rdar://15589470, a bug where + # kCCAlignmentError fails to raise when supplying non-block-aligned + # data. + self._bytes_processed = 0 + if (isinstance(cipher, interfaces.BlockCipherAlgorithm) and not + isinstance(mode, (OFB, CFB, CTR))): + self._byte_block_size = cipher.block_size // 8 + else: + self._byte_block_size = 1 + + registry = self._backend._cipher_registry + try: + adapter = registry[type(cipher), type(mode)] + except KeyError: + raise UnsupportedAlgorithm( + "cipher {0} in {1} mode is not supported " + "by this backend".format( + cipher.name, mode.name if mode else mode) + ) + + cipher_enum, mode_enum = adapter(self._backend, cipher, mode) + ctx = self._backend._ffi.new("CCCryptorRef *") + ctx[0] = self._backend._ffi.gc( + ctx[0], self._backend._lib.CCCryptorRelease + ) + + if isinstance(mode, interfaces.ModeWithInitializationVector): + iv_nonce = mode.initialization_vector + elif isinstance(mode, interfaces.ModeWithNonce): + iv_nonce = mode.nonce + else: + iv_nonce = self._backend._ffi.NULL + + if isinstance(mode, CTR): + mode_option = self._backend._lib.kCCModeOptionCTR_BE + else: + mode_option = 0 + + res = self._backend._lib.CCCryptorCreateWithMode( + operation, + mode_enum, cipher_enum, + self._backend._lib.ccNoPadding, iv_nonce, + cipher.key, len(cipher.key), + self._backend._ffi.NULL, 0, 0, mode_option, ctx) + assert res == self._backend._lib.kCCSuccess + + self._ctx = ctx + + def update(self, data): + # Count bytes processed to handle block alignment. + self._bytes_processed += len(data) + buf = self._backend._ffi.new( + "unsigned char[]", len(data) + self._byte_block_size - 1) + outlen = self._backend._ffi.new("size_t *") + res = self._backend._lib.CCCryptorUpdate( + self._ctx[0], data, len(data), buf, + len(data) + self._byte_block_size - 1, outlen) + assert res == self._backend._lib.kCCSuccess + return self._backend._ffi.buffer(buf)[:outlen[0]] + + def finalize(self): + # Raise error if block alignment is wrong. + if self._bytes_processed % self._byte_block_size: + raise ValueError( + "The length of the provided data is not a multiple of " + "the block length" + ) + buf = self._backend._ffi.new("unsigned char[]", self._byte_block_size) + outlen = self._backend._ffi.new("size_t *") + res = self._backend._lib.CCCryptorFinal( + self._ctx[0], buf, len(buf), outlen) + assert res == self._backend._lib.kCCSuccess + res = self._backend._lib.CCCryptorRelease(self._ctx[0]) + assert res == self._backend._lib.kCCSuccess + return self._backend._ffi.buffer(buf)[:outlen[0]] + @utils.register_interface(interfaces.HashContext) class _HashContext(object): diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py new file mode 100644 index 00000000..1d768ec4 --- /dev/null +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -0,0 +1,31 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from cryptography.hazmat.bindings.commoncrypto.binding import Binding +from cryptography.hazmat.primitives.ciphers.algorithms import AES +from cryptography.hazmat.primitives.ciphers.modes import CBC + + +@pytest.mark.skipif(not Binding.is_available(), + reason="CommonCrypto not available") +class TestCommonCrypto(object): + def test_supports_cipher(self): + from cryptography.hazmat.backends.commoncrypto.backend import backend + assert backend.cipher_supported(None, None) is False + + def test_register_duplicate_cipher_adapter(self): + from cryptography.hazmat.backends.commoncrypto.backend import backend + with pytest.raises(ValueError): + backend.register_cipher_adapter(AES, CBC, None) -- cgit v1.2.3 From ed8d864f5412defe18d54dbf3c027120d4baeebe Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 19 Jan 2014 22:02:43 -0600 Subject: update changelog for commoncrypto backend --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 819b426a..c3859c25 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,7 +6,7 @@ Changelog **In development** -* Added :doc:`/hazmat/backends/commoncrypto` with hash and HMAC support. +* Added :doc:`/hazmat/backends/commoncrypto`. * Added initial :doc:`/hazmat/bindings/commoncrypto`. 0.1 - 2014-01-08 -- cgit v1.2.3 From a33a5e3e17140f8877aafa9db55f69008d4f42d2 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 19 Jan 2014 22:27:00 -0600 Subject: add error handler --- .../hazmat/backends/commoncrypto/backend.py | 23 ++++++++++++++++++---- tests/hazmat/backends/test_commoncrypto.py | 12 +++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index bdaff301..314df0bd 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -152,6 +152,21 @@ class Backend(object): _GetCipherModeEnum() ) + def _check_response(self, response): + if response == self._lib.kCCSuccess: + return + elif response == self._lib.kCCAlignmentError: + # This error is not currently triggered due to a bug filed as + # rdar://15589470 + raise ValueError( + "The length of the provided data is not a multiple of " + "the block length" + ) + else: + raise SystemError( + "The backend returned an error. Code: {0}".format(response) + ) + class _GetCipherModeEnum(object): def __call__(self, backend, cipher, mode): @@ -228,7 +243,7 @@ class _CipherContext(object): self._backend._lib.ccNoPadding, iv_nonce, cipher.key, len(cipher.key), self._backend._ffi.NULL, 0, 0, mode_option, ctx) - assert res == self._backend._lib.kCCSuccess + self._backend._check_response(res) self._ctx = ctx @@ -241,7 +256,7 @@ class _CipherContext(object): res = self._backend._lib.CCCryptorUpdate( self._ctx[0], data, len(data), buf, len(data) + self._byte_block_size - 1, outlen) - assert res == self._backend._lib.kCCSuccess + self._backend._check_response(res) return self._backend._ffi.buffer(buf)[:outlen[0]] def finalize(self): @@ -255,9 +270,9 @@ class _CipherContext(object): outlen = self._backend._ffi.new("size_t *") res = self._backend._lib.CCCryptorFinal( self._ctx[0], buf, len(buf), outlen) - assert res == self._backend._lib.kCCSuccess + self._backend._check_response(res) res = self._backend._lib.CCCryptorRelease(self._ctx[0]) - assert res == self._backend._lib.kCCSuccess + self._backend._check_response(res) return self._backend._ffi.buffer(buf)[:outlen[0]] diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py index 1d768ec4..8b353237 100644 --- a/tests/hazmat/backends/test_commoncrypto.py +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -29,3 +29,15 @@ class TestCommonCrypto(object): from cryptography.hazmat.backends.commoncrypto.backend import backend with pytest.raises(ValueError): backend.register_cipher_adapter(AES, CBC, None) + + def test_handle_response(self): + from cryptography.hazmat.backends.commoncrypto.backend import backend + + with pytest.raises(ValueError): + backend._check_response(backend._lib.kCCAlignmentError) + + with pytest.raises(SystemError): + backend._check_response(backend._lib.kCCMemoryFailure) + + with pytest.raises(SystemError): + backend._check_response(backend._lib.kCCDecodeError) -- cgit v1.2.3 From 404769442e9dab3a0dab67d890cb7441b34fd007 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 19 Jan 2014 23:17:47 -0600 Subject: add comment and change gc for cipher context --- .../hazmat/backends/commoncrypto/backend.py | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 314df0bd..8b48fecc 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -189,6 +189,15 @@ class _GetCipherModeEnum(object): return (cipher_enum, mode_enum) +def _release_cipher_ctx(ctx): + """ + Called by the garbage collector and used to safely dereference and + release the context. + """ + res = backend._lib.CCCryptorRelease(ctx[0]) + backend._check_response(res) + + @utils.register_interface(interfaces.CipherContext) class _CipherContext(object): _ENCRYPT = 0 # kCCEncrypt @@ -199,9 +208,15 @@ class _CipherContext(object): self._cipher = cipher self._mode = mode self._operation = operation - # bytes_processed is needed to work around rdar://15589470, a bug where - # kCCAlignmentError fails to raise when supplying non-block-aligned - # data. + # There is a bug in CommonCrypto where block ciphers do not raise + # kCCAlignmentError when finalizing if you supply non-block aligned + # data. To work around this we need to keep track of the block + # alignment ourselves, but only for alg+mode combos that require + # block alignment. OFB, CFB, and CTR make a block cipher algorithm + # into a stream cipher so we don't need to track them (and thus their + # block size is effectively 1 byte just like OpenSSL/CommonCrypto + # treat RC4 and other stream cipher block sizes). + # This bug has been filed as rdar://15589470 self._bytes_processed = 0 if (isinstance(cipher, interfaces.BlockCipherAlgorithm) and not isinstance(mode, (OFB, CFB, CTR))): @@ -221,9 +236,7 @@ class _CipherContext(object): cipher_enum, mode_enum = adapter(self._backend, cipher, mode) ctx = self._backend._ffi.new("CCCryptorRef *") - ctx[0] = self._backend._ffi.gc( - ctx[0], self._backend._lib.CCCryptorRelease - ) + ctx = self._backend._ffi.gc(ctx, self._release_cipher_ctx) if isinstance(mode, interfaces.ModeWithInitializationVector): iv_nonce = mode.initialization_vector -- cgit v1.2.3 From 263a3352159cac0073df7d23c9dbbc6c0749d9aa Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 20 Jan 2014 00:07:34 -0600 Subject: change cipher registry to store enums --- .../hazmat/backends/commoncrypto/backend.py | 64 ++++++++++------------ tests/hazmat/backends/test_commoncrypto.py | 5 +- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 8b48fecc..a9ffba4e 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -120,36 +120,52 @@ class Backend(object): def create_symmetric_decryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) - def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): + def register_cipher_adapter(self, cipher_cls, cipher_const, mode_cls, + mode_const): if (cipher_cls, mode_cls) in self._cipher_registry: raise ValueError("Duplicate registration for: {0} {1}".format( cipher_cls, mode_cls) ) - self._cipher_registry[cipher_cls, mode_cls] = adapter + self._cipher_registry[cipher_cls, mode_cls] = (cipher_const, + mode_const) def _register_default_ciphers(self): - for mode_cls in [CBC, ECB, CFB, OFB, CTR]: + for mode_cls, mode_const in [ + (CBC, self._lib.kCCModeCBC), (ECB, self._lib.kCCModeECB), + (CFB, self._lib.kCCModeCFB), (OFB, self._lib.kCCModeOFB), + (CTR, self._lib.kCCModeCTR) + ]: self.register_cipher_adapter( AES, + self._lib.kCCAlgorithmAES128, mode_cls, - _GetCipherModeEnum() + mode_const ) - for mode_cls in [CBC, CFB, OFB]: + for mode_cls, mode_const in [ + (CBC, self._lib.kCCModeCBC), (CFB, self._lib.kCCModeCFB), + (OFB, self._lib.kCCModeOFB), + ]: self.register_cipher_adapter( TripleDES, + self._lib.kCCAlgorithm3DES, mode_cls, - _GetCipherModeEnum() + mode_const ) - for mode_cls in [CBC, CFB, OFB, ECB]: + for mode_cls, mode_const in [ + (CBC, self._lib.kCCModeCBC), (ECB, self._lib.kCCModeECB), + (CFB, self._lib.kCCModeCFB), (OFB, self._lib.kCCModeOFB), + ]: self.register_cipher_adapter( Blowfish, + self._lib.kCCAlgorithmBlowfish, mode_cls, - _GetCipherModeEnum() + mode_const ) self.register_cipher_adapter( ARC4, + self._lib.kCCAlgorithmRC4, type(None), - _GetCipherModeEnum() + self._lib.kCCModeRC4 ) def _check_response(self, response): @@ -168,27 +184,6 @@ class Backend(object): ) -class _GetCipherModeEnum(object): - def __call__(self, backend, cipher, mode): - cipher_enum = { - AES: backend._lib.kCCAlgorithmAES128, - TripleDES: backend._lib.kCCAlgorithm3DES, - Blowfish: backend._lib.kCCAlgorithmBlowfish, - ARC4: backend._lib.kCCAlgorithmRC4, - }[type(cipher)] - - mode_enum = { - ECB: backend._lib.kCCModeECB, - CBC: backend._lib.kCCModeCBC, - CTR: backend._lib.kCCModeCTR, - CFB: backend._lib.kCCModeCFB, - OFB: backend._lib.kCCModeOFB, - type(None): backend._lib.kCCModeRC4, - }[type(mode)] - - return (cipher_enum, mode_enum) - - def _release_cipher_ctx(ctx): """ Called by the garbage collector and used to safely dereference and @@ -226,7 +221,7 @@ class _CipherContext(object): registry = self._backend._cipher_registry try: - adapter = registry[type(cipher), type(mode)] + cipher_enum, mode_enum = registry[type(cipher), type(mode)] except KeyError: raise UnsupportedAlgorithm( "cipher {0} in {1} mode is not supported " @@ -234,9 +229,8 @@ class _CipherContext(object): cipher.name, mode.name if mode else mode) ) - cipher_enum, mode_enum = adapter(self._backend, cipher, mode) ctx = self._backend._ffi.new("CCCryptorRef *") - ctx = self._backend._ffi.gc(ctx, self._release_cipher_ctx) + ctx = self._backend._ffi.gc(ctx, _release_cipher_ctx) if isinstance(mode, interfaces.ModeWithInitializationVector): iv_nonce = mode.initialization_vector @@ -284,8 +278,8 @@ class _CipherContext(object): res = self._backend._lib.CCCryptorFinal( self._ctx[0], buf, len(buf), outlen) self._backend._check_response(res) - res = self._backend._lib.CCCryptorRelease(self._ctx[0]) - self._backend._check_response(res) + # TODO: how do we release this here without causing a crash when + # the GC also releases it? return self._backend._ffi.buffer(buf)[:outlen[0]] diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py index 8b353237..8022863c 100644 --- a/tests/hazmat/backends/test_commoncrypto.py +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -28,7 +28,10 @@ class TestCommonCrypto(object): def test_register_duplicate_cipher_adapter(self): from cryptography.hazmat.backends.commoncrypto.backend import backend with pytest.raises(ValueError): - backend.register_cipher_adapter(AES, CBC, None) + backend.register_cipher_adapter( + AES, backend._lib.kCCAlgorithmAES128, + CBC, backend._lib.kCCModeCBC + ) def test_handle_response(self): from cryptography.hazmat.backends.commoncrypto.backend import backend -- cgit v1.2.3 From 5ab6a208c46f1de6e261646a0ad34482ea755922 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 20 Jan 2014 12:03:46 -0600 Subject: make register_cipher_adapter private and fix cryptorref release --- .../hazmat/backends/commoncrypto/backend.py | 21 +++++++++++---------- tests/hazmat/backends/test_commoncrypto.py | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index a9ffba4e..cdce5f43 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -120,8 +120,8 @@ class Backend(object): def create_symmetric_decryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) - def register_cipher_adapter(self, cipher_cls, cipher_const, mode_cls, - mode_const): + def _register_cipher_adapter(self, cipher_cls, cipher_const, mode_cls, + mode_const): if (cipher_cls, mode_cls) in self._cipher_registry: raise ValueError("Duplicate registration for: {0} {1}".format( cipher_cls, mode_cls) @@ -135,7 +135,7 @@ class Backend(object): (CFB, self._lib.kCCModeCFB), (OFB, self._lib.kCCModeOFB), (CTR, self._lib.kCCModeCTR) ]: - self.register_cipher_adapter( + self._register_cipher_adapter( AES, self._lib.kCCAlgorithmAES128, mode_cls, @@ -145,7 +145,7 @@ class Backend(object): (CBC, self._lib.kCCModeCBC), (CFB, self._lib.kCCModeCFB), (OFB, self._lib.kCCModeOFB), ]: - self.register_cipher_adapter( + self._register_cipher_adapter( TripleDES, self._lib.kCCAlgorithm3DES, mode_cls, @@ -155,13 +155,13 @@ class Backend(object): (CBC, self._lib.kCCModeCBC), (ECB, self._lib.kCCModeECB), (CFB, self._lib.kCCModeCFB), (OFB, self._lib.kCCModeOFB), ]: - self.register_cipher_adapter( + self._register_cipher_adapter( Blowfish, self._lib.kCCAlgorithmBlowfish, mode_cls, mode_const ) - self.register_cipher_adapter( + self._register_cipher_adapter( ARC4, self._lib.kCCAlgorithmRC4, type(None), @@ -189,8 +189,10 @@ def _release_cipher_ctx(ctx): Called by the garbage collector and used to safely dereference and release the context. """ - res = backend._lib.CCCryptorRelease(ctx[0]) - backend._check_response(res) + if ctx[0] != backend._ffi.NULL: + res = backend._lib.CCCryptorRelease(ctx[0]) + backend._check_response(res) + ctx[0] = backend._ffi.NULL @utils.register_interface(interfaces.CipherContext) @@ -278,8 +280,7 @@ class _CipherContext(object): res = self._backend._lib.CCCryptorFinal( self._ctx[0], buf, len(buf), outlen) self._backend._check_response(res) - # TODO: how do we release this here without causing a crash when - # the GC also releases it? + _release_cipher_ctx(self._ctx) return self._backend._ffi.buffer(buf)[:outlen[0]] diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py index 8022863c..68ab6bc1 100644 --- a/tests/hazmat/backends/test_commoncrypto.py +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -28,7 +28,7 @@ class TestCommonCrypto(object): def test_register_duplicate_cipher_adapter(self): from cryptography.hazmat.backends.commoncrypto.backend import backend with pytest.raises(ValueError): - backend.register_cipher_adapter( + backend._register_cipher_adapter( AES, backend._lib.kCCAlgorithmAES128, CBC, backend._lib.kCCModeCBC ) -- cgit v1.2.3 From b20689fc9b1841e8e190b80087c9857440dcc10a Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 20 Jan 2014 17:00:37 -0600 Subject: style nit fix --- cryptography/hazmat/backends/commoncrypto/backend.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 8b9fe084..ed5376c8 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -133,8 +133,10 @@ class Backend(object): def _register_default_ciphers(self): for mode_cls, mode_const in [ - (CBC, self._lib.kCCModeCBC), (ECB, self._lib.kCCModeECB), - (CFB, self._lib.kCCModeCFB), (OFB, self._lib.kCCModeOFB), + (CBC, self._lib.kCCModeCBC), + (ECB, self._lib.kCCModeECB), + (CFB, self._lib.kCCModeCFB), + (OFB, self._lib.kCCModeOFB), (CTR, self._lib.kCCModeCTR) ]: self._register_cipher_adapter( @@ -154,8 +156,10 @@ class Backend(object): mode_const ) for mode_cls, mode_const in [ - (CBC, self._lib.kCCModeCBC), (ECB, self._lib.kCCModeECB), - (CFB, self._lib.kCCModeCFB), (OFB, self._lib.kCCModeOFB), + (CBC, self._lib.kCCModeCBC), + (ECB, self._lib.kCCModeECB), + (CFB, self._lib.kCCModeCFB), + (OFB, self._lib.kCCModeOFB) ]: self._register_cipher_adapter( Blowfish, -- cgit v1.2.3 From 8d7b96e8a4fb5766e72e45668dae9369f0070f35 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 20 Jan 2014 20:42:55 -0600 Subject: missed one. of course. --- cryptography/hazmat/backends/commoncrypto/backend.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index ed5376c8..2d142569 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -146,7 +146,8 @@ class Backend(object): mode_const ) for mode_cls, mode_const in [ - (CBC, self._lib.kCCModeCBC), (CFB, self._lib.kCCModeCFB), + (CBC, self._lib.kCCModeCBC), + (CFB, self._lib.kCCModeCFB), (OFB, self._lib.kCCModeOFB), ]: self._register_cipher_adapter( -- cgit v1.2.3 From 597cc64acd54ed9053e9aa0d83092b1ee1ba74e2 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 20 Jan 2014 22:18:44 -0600 Subject: Starting now, setuptools should come with pip *fingerscrossed* --- .travis/install.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis/install.sh b/.travis/install.sh index 1e3b1702..b6dd5acc 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -29,13 +29,11 @@ if [[ "$(uname -s)" == "Darwin" ]]; then py26) curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py sudo python get-pip.py - sudo pip install setuptools --no-use-wheel --upgrade sudo pip install virtualenv ;; py27) curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py sudo python get-pip.py - sudo pip install setuptools --no-use-wheel --upgrade sudo pip install virtualenv ;; pypy) -- cgit v1.2.3 From 0f82a6698af65f3786fb597636d09f25fbcc49de Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 22 Jan 2014 10:04:41 -0600 Subject: support for aes keywrap (RFC 3394) in the bindings --- cryptography/hazmat/bindings/openssl/aes.py | 40 +++++++++++++++++++++++++ cryptography/hazmat/bindings/openssl/binding.py | 1 + 2 files changed, 41 insertions(+) create mode 100644 cryptography/hazmat/bindings/openssl/aes.py diff --git a/cryptography/hazmat/bindings/openssl/aes.py b/cryptography/hazmat/bindings/openssl/aes.py new file mode 100644 index 00000000..6cbcd577 --- /dev/null +++ b/cryptography/hazmat/bindings/openssl/aes.py @@ -0,0 +1,40 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDES = """ +#include +""" + +TYPES = """ +struct aes_key_st { + ...; +}; +typedef struct aes_key_st AES_KEY; +""" + +FUNCTIONS = """ +int AES_set_encrypt_key(const unsigned char *, const int, AES_KEY *); +int AES_set_decrypt_key(const unsigned char *, const int, AES_KEY *); +int AES_wrap_key(AES_KEY *, const unsigned char *, unsigned char *, + const unsigned char *, unsigned int); +int AES_unwrap_key(AES_KEY *, const unsigned char *, unsigned char *, + const unsigned char *, unsigned int); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 88299d14..4dedd816 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -41,6 +41,7 @@ class Binding(object): """ _module_prefix = "cryptography.hazmat.bindings.openssl." _modules = [ + "aes", "asn1", "bignum", "bio", -- cgit v1.2.3 From 40c7f9eb8eafe9eac39f86d6f36233375e938cdb Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 22 Jan 2014 13:15:12 -0600 Subject: GCM bindings for CommonCrypto --- cryptography/hazmat/bindings/commoncrypto/common_cryptor.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py b/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py index ef0e7e10..e8b6cf91 100644 --- a/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py +++ b/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py @@ -83,6 +83,15 @@ CCCryptorStatus CCCryptorUpdate(CCCryptorRef, const void *, size_t, void *, size_t, size_t *); CCCryptorStatus CCCryptorFinal(CCCryptorRef, void *, size_t, size_t *); CCCryptorStatus CCCryptorRelease(CCCryptorRef); + +CCCryptorStatus CCCryptorGCMAddIV(CCCryptorRef, const void *, size_t); +CCCryptorStatus CCCryptorGCMAddAAD(CCCryptorRef, const void *, size_t); +CCCryptorStatus CCCryptorGCMEncrypt(CCCryptorRef, const void *, size_t, + void *); +CCCryptorStatus CCCryptorGCMDecrypt(CCCryptorRef, const void *, size_t, + void *); +CCCryptorStatus CCCryptorGCMFinal(CCCryptorRef, const void *, size_t *); +CCCryptorStatus CCCryptorGCMReset(CCCryptorRef); """ MACROS = """ -- cgit v1.2.3 From cd9dc6d9e4253fcac423114c26a7ca32dfe7ac05 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 22 Jan 2014 16:14:26 -0600 Subject: add kCCModeGCM --- cryptography/hazmat/bindings/commoncrypto/common_cryptor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py b/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py index e8b6cf91..8f03bc3f 100644 --- a/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py +++ b/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py @@ -62,6 +62,7 @@ enum { kCCModeXTS = 8, kCCModeRC4 = 9, kCCModeCFB8 = 10, + kCCModeGCM = 11 }; typedef uint32_t CCMode; enum { @@ -98,6 +99,10 @@ MACROS = """ """ CUSTOMIZATIONS = """ +// Not defined in the public header +enum { + kCCModeGCM = 11 +}; """ CONDITIONAL_NAMES = {} -- cgit v1.2.3 From 9eca43c41000bd3e7985e39854881e3a61e99803 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 22 Jan 2014 16:14:54 -0600 Subject: GCM support for CommonCrypto backend --- .../hazmat/backends/commoncrypto/backend.py | 103 +++++++++++++++++++-- tests/hazmat/backends/test_commoncrypto.py | 21 ++++- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 2d142569..1c025ece 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -16,17 +16,17 @@ from __future__ import absolute_import, division, print_function from collections import namedtuple from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.exceptions import UnsupportedAlgorithm, InvalidTag from cryptography.hazmat.backends.interfaces import ( HashBackend, HMACBackend, CipherBackend ) from cryptography.hazmat.bindings.commoncrypto.binding import Binding -from cryptography.hazmat.primitives import interfaces +from cryptography.hazmat.primitives import interfaces, constant_time from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, Blowfish, TripleDES, ARC4 ) from cryptography.hazmat.primitives.ciphers.modes import ( - CBC, CTR, ECB, OFB, CFB + CBC, CTR, ECB, OFB, CFB, GCM ) @@ -117,10 +117,20 @@ class Backend(object): return True def create_symmetric_encryption_ctx(self, cipher, mode): - return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) + if isinstance(mode, GCM): + return _GCMCipherContext( + self, cipher, mode, _GCMCipherContext._ENCRYPT + ) + else: + return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) def create_symmetric_decryption_ctx(self, cipher, mode): - return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + if isinstance(mode, GCM): + return _GCMCipherContext( + self, cipher, mode, _GCMCipherContext._DECRYPT + ) + else: + return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) def _register_cipher_adapter(self, cipher_cls, cipher_const, mode_cls, mode_const): @@ -137,7 +147,8 @@ class Backend(object): (ECB, self._lib.kCCModeECB), (CFB, self._lib.kCCModeCFB), (OFB, self._lib.kCCModeOFB), - (CTR, self._lib.kCCModeCTR) + (CTR, self._lib.kCCModeCTR), + (GCM, self._lib.kCCModeGCM) ]: self._register_cipher_adapter( AES, @@ -291,6 +302,86 @@ class _CipherContext(object): return self._backend._ffi.buffer(buf)[:outlen[0]] +@utils.register_interface(interfaces.AEADCipherContext) +@utils.register_interface(interfaces.AEADEncryptionContext) +class _GCMCipherContext(object): + _ENCRYPT = 0 # kCCEncrypt + _DECRYPT = 1 # kCCDecrypt + + def __init__(self, backend, cipher, mode, operation): + self._backend = backend + self._cipher = cipher + self._mode = mode + self._operation = operation + self._tag = None + + registry = self._backend._cipher_registry + try: + cipher_enum, mode_enum = registry[type(cipher), type(mode)] + except KeyError: + raise UnsupportedAlgorithm( + "cipher {0} in {1} mode is not supported " + "by this backend".format( + cipher.name, mode.name if mode else mode) + ) + + ctx = self._backend._ffi.new("CCCryptorRef *") + ctx = self._backend._ffi.gc(ctx, _release_cipher_ctx) + + self._ctx = ctx + + res = self._backend._lib.CCCryptorCreateWithMode( + operation, + mode_enum, cipher_enum, + self._backend._lib.ccNoPadding, + self._backend._ffi.NULL, + cipher.key, len(cipher.key), + self._backend._ffi.NULL, 0, 0, 0, self._ctx) + self._backend._check_response(res) + + res = self._backend._lib.CCCryptorGCMAddIV( + self._ctx[0], + mode.initialization_vector, + len(mode.initialization_vector) + ) + self._backend._check_response(res) + + def update(self, data): + buf = self._backend._ffi.new("unsigned char[]", len(data)) + args = (self._ctx[0], data, len(data), buf) + if self._operation == self._ENCRYPT: + res = self._backend._lib.CCCryptorGCMEncrypt(*args) + else: + res = self._backend._lib.CCCryptorGCMDecrypt(*args) + + self._backend._check_response(res) + return self._backend._ffi.buffer(buf)[:len(data)] + + def finalize(self): + tag_size = self._cipher.block_size // 8 + tag_buf = self._backend._ffi.new("unsigned char[]", tag_size) + tag_len = self._backend._ffi.new("size_t *", tag_size) + res = backend._lib.CCCryptorGCMFinal(self._ctx[0], tag_buf, tag_len) + self._backend._check_response(res) + _release_cipher_ctx(self._ctx) + self._tag = self._backend._ffi.buffer(tag_buf)[:tag_size] + if self._operation == self._DECRYPT and not constant_time.bytes_eq( + self._tag[:len(self._mode.tag)], self._mode.tag + ): + raise InvalidTag + return b"" + + def authenticate_additional_data(self, data): + res = self._backend._lib.CCCryptorGCMAddAAD( + self._ctx[0], data, len(data) + ) + self._backend._check_response(res) + + @property + def tag(self): + return self._tag + + @utils.register_interface(interfaces.HashContext) class _HashContext(object): def __init__(self, backend, algorithm, ctx=None): diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py index 68ab6bc1..cfa332d0 100644 --- a/tests/hazmat/backends/test_commoncrypto.py +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -13,9 +13,19 @@ import pytest +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.bindings.commoncrypto.binding import Binding +from cryptography.hazmat.primitives import interfaces from cryptography.hazmat.primitives.ciphers.algorithms import AES -from cryptography.hazmat.primitives.ciphers.modes import CBC +from cryptography.hazmat.primitives.ciphers.base import Cipher +from cryptography.hazmat.primitives.ciphers.modes import CBC, GCM + + +@utils.register_interface(interfaces.CipherAlgorithm) +class DummyCipher(object): + name = "dummy-cipher" + block_size = 128 @pytest.mark.skipif(not Binding.is_available(), @@ -44,3 +54,12 @@ class TestCommonCrypto(object): with pytest.raises(SystemError): backend._check_response(backend._lib.kCCDecodeError) + + def test_nonexistent_aead_cipher(self): + from cryptography.hazmat.backends.commoncrypto.backend import Backend + b = Backend() + cipher = Cipher( + DummyCipher(), GCM(b"fake_iv_here"), backend=b, + ) + with pytest.raises(UnsupportedAlgorithm): + cipher.encryptor() -- cgit v1.2.3 From f839016c0b1e5f08c7606c4a90a9fbabc439c1e7 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 22 Jan 2014 17:22:07 -0600 Subject: remove encrypt/decrypt attributes on CommonCrypto cipher classes Address some other review comments --- .../hazmat/backends/commoncrypto/backend.py | 27 +++++++++------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 1c025ece..ee2e000c 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -119,18 +119,18 @@ class Backend(object): def create_symmetric_encryption_ctx(self, cipher, mode): if isinstance(mode, GCM): return _GCMCipherContext( - self, cipher, mode, _GCMCipherContext._ENCRYPT + self, cipher, mode, self._lib.kCCEncrypt ) else: - return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) + return _CipherContext(self, cipher, mode, self._lib.kCCEncrypt) def create_symmetric_decryption_ctx(self, cipher, mode): if isinstance(mode, GCM): return _GCMCipherContext( - self, cipher, mode, _GCMCipherContext._DECRYPT + self, cipher, mode, self._lib.kCCDecrypt ) else: - return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + return _CipherContext(self, cipher, mode, self._lib.kCCDecrypt) def _register_cipher_adapter(self, cipher_cls, cipher_const, mode_cls, mode_const): @@ -215,9 +215,6 @@ def _release_cipher_ctx(ctx): @utils.register_interface(interfaces.CipherContext) class _CipherContext(object): - _ENCRYPT = 0 # kCCEncrypt - _DECRYPT = 1 # kCCDecrypt - def __init__(self, backend, cipher, mode, operation): self._backend = backend self._cipher = cipher @@ -305,9 +302,6 @@ class _CipherContext(object): @utils.register_interface(interfaces.AEADCipherContext) @utils.register_interface(interfaces.AEADEncryptionContext) class _GCMCipherContext(object): - _ENCRYPT = 0 # kCCEncrypt - _DECRYPT = 1 # kCCDecrypt - def __init__(self, backend, cipher, mode, operation): self._backend = backend self._cipher = cipher @@ -349,13 +343,13 @@ class _GCMCipherContext(object): def update(self, data): buf = self._backend._ffi.new("unsigned char[]", len(data)) args = (self._ctx[0], data, len(data), buf) - if self._operation == self._ENCRYPT: + if self._operation == self._backend._lib.kCCEncrypt: res = self._backend._lib.CCCryptorGCMEncrypt(*args) else: res = self._backend._lib.CCCryptorGCMDecrypt(*args) self._backend._check_response(res) - return self._backend._ffi.buffer(buf)[:len(data)] + return self._backend._ffi.buffer(buf)[:] def finalize(self): tag_size = self._cipher.block_size // 8 @@ -364,10 +358,11 @@ class _GCMCipherContext(object): res = backend._lib.CCCryptorGCMFinal(self._ctx[0], tag_buf, tag_len) self._backend._check_response(res) _release_cipher_ctx(self._ctx) - self._tag = self._backend._ffi.buffer(tag_buf)[:tag_size] - if self._operation == self._DECRYPT and not constant_time.bytes_eq( - self._tag[:len(self._mode.tag)], self._mode.tag - ): + self._tag = self._backend._ffi.buffer(tag_buf)[:] + if (self._operation == self._backend._lib.kCCDecrypt and + not constant_time.bytes_eq( + self._tag[:len(self._mode.tag)], self._mode.tag + )): raise InvalidTag return b"" -- cgit v1.2.3 From 0437e8d0c85f0cf365026fac03cb32576492d406 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 22 Jan 2014 18:09:15 -0600 Subject: comma for flair --- cryptography/hazmat/backends/commoncrypto/backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index ee2e000c..7193f1d8 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -148,7 +148,7 @@ class Backend(object): (CFB, self._lib.kCCModeCFB), (OFB, self._lib.kCCModeOFB), (CTR, self._lib.kCCModeCTR), - (GCM, self._lib.kCCModeGCM) + (GCM, self._lib.kCCModeGCM), ]: self._register_cipher_adapter( AES, -- cgit v1.2.3 From 2dc0c712ca4c7cc8a42e37aa4a14f0b698e1e5d8 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 22 Jan 2014 21:48:05 -0600 Subject: add PBKDF2 from OpenSSL (for future KDF to live alongside HKDF) --- cryptography/hazmat/bindings/openssl/evp.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cryptography/hazmat/bindings/openssl/evp.py b/cryptography/hazmat/bindings/openssl/evp.py index c426e52e..c7cc154f 100644 --- a/cryptography/hazmat/bindings/openssl/evp.py +++ b/cryptography/hazmat/bindings/openssl/evp.py @@ -40,6 +40,7 @@ static const int EVP_CTRL_GCM_GET_TAG; static const int EVP_CTRL_GCM_SET_TAG; static const int Cryptography_HAS_GCM; +static const int Cryptography_HAS_PBKDF2_HMAC; """ FUNCTIONS = """ @@ -95,6 +96,9 @@ int EVP_VerifyFinal(EVP_MD_CTX *, const unsigned char *, unsigned int, EVP_PKEY *); const EVP_MD *EVP_md5(void); + +int PKCS5_PBKDF2_HMAC_SHA1(const char *, int, const unsigned char *, int, int, + int, unsigned char *); """ MACROS = """ @@ -103,6 +107,9 @@ int EVP_PKEY_assign_RSA(EVP_PKEY *, RSA *); int EVP_PKEY_assign_DSA(EVP_PKEY *, DSA *); int EVP_CIPHER_CTX_block_size(const EVP_CIPHER_CTX *); int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *, int, int, void *); + +int PKCS5_PBKDF2_HMAC(const char *, int, const unsigned char *, int, int, + const EVP_MD *, int, unsigned char *); """ CUSTOMIZATIONS = """ @@ -114,6 +121,13 @@ const long EVP_CTRL_GCM_GET_TAG = -1; const long EVP_CTRL_GCM_SET_TAG = -1; const long EVP_CTRL_GCM_SET_IVLEN = -1; #endif +#if OPENSSL_VERSION_NUMBER >= 0x10000000 +const long Cryptography_HAS_PBKDF2_HMAC = 1; +#else +const long Cryptography_HAS_PBKDF2_HMAC = 0; +int (*PKCS5_PBKDF2_HMAC)(const char *, int, const unsigned char *, int, int, + const EVP_MD *, int, unsigned char *) = NULL; +#endif """ CONDITIONAL_NAMES = { @@ -121,5 +135,8 @@ CONDITIONAL_NAMES = { "EVP_CTRL_GCM_GET_TAG", "EVP_CTRL_GCM_SET_TAG", "EVP_CTRL_GCM_SET_IVLEN", + ], + "Cryptography_HAS_PBKDF2_HMAC": [ + "PKCS5_PBKDF2_HMAC" ] } -- cgit v1.2.3 From 3ae3d90e606f660aa4bc64a03619b2c8cbc359e1 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 22 Jan 2014 21:56:35 -0600 Subject: add commoncrypto PBKDF2 bindings --- .../hazmat/bindings/commoncrypto/binding.py | 1 + .../bindings/commoncrypto/common_key_derivation.py | 48 ++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 cryptography/hazmat/bindings/commoncrypto/common_key_derivation.py diff --git a/cryptography/hazmat/bindings/commoncrypto/binding.py b/cryptography/hazmat/bindings/commoncrypto/binding.py index a5a0dca8..45c0eaad 100644 --- a/cryptography/hazmat/bindings/commoncrypto/binding.py +++ b/cryptography/hazmat/bindings/commoncrypto/binding.py @@ -26,6 +26,7 @@ class Binding(object): _modules = [ "common_digest", "common_hmac", + "common_key_derivation", "common_cryptor", ] diff --git a/cryptography/hazmat/bindings/commoncrypto/common_key_derivation.py b/cryptography/hazmat/bindings/commoncrypto/common_key_derivation.py new file mode 100644 index 00000000..85def1e9 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/common_key_derivation.py @@ -0,0 +1,48 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDES = """ +#include +""" + +TYPES = """ +enum { + kCCPBKDF2 = 2, +}; +typedef uint32_t CCPBKDFAlgorithm; +enum { + kCCPRFHmacAlgSHA1 = 1, + kCCPRFHmacAlgSHA224 = 2, + kCCPRFHmacAlgSHA256 = 3, + kCCPRFHmacAlgSHA384 = 4, + kCCPRFHmacAlgSHA512 = 5, +}; +typedef uint32_t CCPseudoRandomAlgorithm; +typedef unsigned int uint; +""" + +FUNCTIONS = """ +int CCKeyDerivationPBKDF(CCPBKDFAlgorithm, const char *, size_t, + const uint8_t *, size_t, CCPseudoRandomAlgorithm, + uint, uint8_t *, size_t); +uint CCCalibratePBKDF(CCPBKDFAlgorithm, size_t, size_t, + CCPseudoRandomAlgorithm, size_t, uint32_t); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} -- cgit v1.2.3 From 3c1eb12115e193285aaf790d095c300f5dcf7df1 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 23 Jan 2014 07:46:21 -0600 Subject: Capital S is the canonical URL --- docs/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index 4bb1461d..3de41fd5 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -287,5 +287,5 @@ The HTML documentation index can now be found at .. _`tox`: https://pypi.python.org/pypi/tox .. _`virtualenv`: https://pypi.python.org/pypi/virtualenv .. _`pip`: https://pypi.python.org/pypi/pip -.. _`sphinx`: https://pypi.python.org/pypi/sphinx +.. _`sphinx`: https://pypi.python.org/pypi/Sphinx .. _`reStructured Text`: http://sphinx-doc.org/rest.html -- cgit v1.2.3 From a8fc6f32a5b9c8a5d055524a47c32ffa22530dcf Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 23 Jan 2014 10:48:16 -0600 Subject: Document where to get OpenSSL on windows --- docs/index.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index e17b4f9f..1100445f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,12 @@ You can install ``cryptography`` with ``pip``: $ pip install cryptography +.. note:: + + If you're on Windows you'll need to make sure you have OpenSSL installed. + There are `pre-compiled binaries`_ available. + + Why a new crypto library for Python? ------------------------------------ @@ -81,3 +87,6 @@ The ``cryptography`` open source project doing-a-release changelog community + + +.. _`pre-compiled binaries`: http://slproweb.com/products/Win32OpenSSL.html -- cgit v1.2.3 From e7651def41eb46053a7c5bc257ac146dc70b5025 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 23 Jan 2014 11:34:35 -0600 Subject: Link the official page --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 1100445f..a1cebc30 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -89,4 +89,4 @@ The ``cryptography`` open source project community -.. _`pre-compiled binaries`: http://slproweb.com/products/Win32OpenSSL.html +.. _`pre-compiled binaries`: https://www.openssl.org/related/binaries.html -- cgit v1.2.3 From 580e883c4cf93c82460fd0e8308e68fe71f3342d Mon Sep 17 00:00:00 2001 From: David Reid Date: Thu, 23 Jan 2014 10:02:23 -0800 Subject: Add HKDF vectors from RFC5869. --- .../primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt diff --git a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt new file mode 100644 index 00000000..598ecaae --- /dev/null +++ b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt @@ -0,0 +1,48 @@ +# A.4. Test Case 4 +# Basic test case with SHA-1 + +Hash = SHA-1 +IKM = 0b0b0b0b0b0b0b0b0b0b0b +salt = 000102030405060708090a0b0c +info = f0f1f2f3f4f5f6f7f8f9 +L = 42 + +PRK = 9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243 +OKM = 085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896 + +# A.5. Test Case 5 +# Test with SHA-1 and longer inputs/outputs + +Hash = SHA-1 +IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f +salt = 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf +info = b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff +L = 82 + +PRK = 8adae09a2a307059478d309b26c4115a224cfaf6 +OKM = 0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4 + +# A.6. Test Case 6 +# Test with SHA-1 and zero-length salt/info + +Hash = SHA-1 +IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b +salt = +info = +L = 42 + +PRK = da8c8a73c7fa77288ec6f5e7c297786aa0d32d01 +OKM = 0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918 + +# A.7. Test Case 7 +# Test with SHA-1, salt not provided (defaults to HashLen zero octets), +# zero-length info + +Hash = SHA-1 +IKM = 0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c +salt = +info = +L = 42 + +PRK = 2adccada18779e7c2077ad2eb19d3f3e731385dd +OKM = 2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc48 -- cgit v1.2.3 From ec73839168a143935c202bebb54249123b20668b Mon Sep 17 00:00:00 2001 From: David Reid Date: Thu, 23 Jan 2014 10:04:35 -0800 Subject: SHA256 vectors. --- .../vectors/KDF/rfc-5869-HKDF-SHA256.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt diff --git a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt new file mode 100644 index 00000000..170dd13e --- /dev/null +++ b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt @@ -0,0 +1,34 @@ +# A.1. Test Case 1 +# Basic test case with SHA-256 + +Hash = SHA-256 +IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b +salt = 000102030405060708090a0b0c +info = f0f1f2f3f4f5f6f7f8f9 +L = 42 +PRK = 077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5 +OKM = 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865 + +# A.2. Test Case 2 +# Test with SHA-256 and longer inputs/outputs + +Hash = SHA-256 +IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f +salt = 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf +info = b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff +L = 82 + +PRK = 06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244 +OKM = b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87 + +# A.3. Test Case 3 +# Test with SHA-256 and zero-length salt/info + +Hash = SHA-256 +IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b +salt = +info = +L = 42 + +PRK = 19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04 +OKM = 8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8 -- cgit v1.2.3 From b32b491a9d976165a1b8ca8565a272ce46fc2730 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 23 Jan 2014 16:24:13 -0600 Subject: DOcument the primitives used in Fernet --- docs/fernet.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/fernet.rst b/docs/fernet.rst index 13295c0c..b0215e32 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -72,5 +72,22 @@ symmetric (also known as "secret key") authenticated cryptography. See :meth:`Fernet.decrypt` for more information. +Implementation +-------------- + +Fernet is built on top of a number of standard cryptographic primitives. +Specifically it uses: + +* :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` in + :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode with a + 128-bit key for encryption; using + :class:`~cryptography.hazmat.primitives.ciphers.PKCS7` padding. +* :class:`~cryptography.hazmat.primitives.hmac.HMAC` using + :class:`~cryptography.hazmat.primitives.hashes.SHA256` for authentication. +* Initialization vectors are generated using ``os.urandom()``. + +For complete details consult the `specification`_. + .. _`Fernet`: https://github.com/fernet/spec/ +.. _`specification`: https://github.com/fernet/spec/blob/master/Spec.md -- cgit v1.2.3 From f84ca678b1a09a0e7ddcdb14a2c938a814f7dca5 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 23 Jan 2014 19:07:07 -0600 Subject: add library switch for windows support Temporarily using pragma: no cover on it until we have windows coverage on travis. Windows builds will be done via jenkins for now. --- .coveragerc | 1 + cryptography/hazmat/bindings/openssl/binding.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 20e3224e..03fc621e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,5 +3,6 @@ branch = True [report] exclude_lines = + pragma: no cover @abc.abstractmethod @abc.abstractproperty diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 4dedd816..f30b551a 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -13,6 +13,8 @@ from __future__ import absolute_import, division, print_function +import sys + from cryptography.hazmat.bindings.utils import build_ffi @@ -79,9 +81,15 @@ class Binding(object): if cls.ffi is not None and cls.lib is not None: return + # platform check to set the right library names + if sys.platform != "win32": + libraries = ["crypto", "ssl"] + else: # pragma: no cover + libraries = ["libeay32", "ssleay32", "advapi32"] + cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules, _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE, - ["crypto", "ssl"]) + libraries) @classmethod def is_available(cls): -- cgit v1.2.3 From 8942efae8a39f63779a4721c22a4dd961ecc537c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 23 Jan 2014 20:54:34 -0600 Subject: remove advapi32 until we need it (for cryptgenrandom) --- cryptography/hazmat/bindings/openssl/binding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index f30b551a..1c17a5b2 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -85,7 +85,7 @@ class Binding(object): if sys.platform != "win32": libraries = ["crypto", "ssl"] else: # pragma: no cover - libraries = ["libeay32", "ssleay32", "advapi32"] + libraries = ["libeay32", "ssleay32"] cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules, _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE, -- cgit v1.2.3 From b495790a40a530c63b231fee9c5f21cae8a85648 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 23 Jan 2014 21:12:19 -0600 Subject: add windows support to changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4b5a5be8..c8843821 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,7 @@ Changelog * Added initial :doc:`/hazmat/bindings/commoncrypto`. * Removed ``register_cipher_adapter`` method from :class:`~cryptography.hazmat.backends.interfaces.CipherBackend`. +* Added support for the OpenSSL backend under Windows. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 17ed58daa9573458157b02f822f5dc471d954298 Mon Sep 17 00:00:00 2001 From: Alex Stapleton Date: Tue, 21 Jan 2014 20:19:17 +0000 Subject: Python implementation of OpenSSL locking callback --- cryptography/hazmat/backends/openssl/backend.py | 2 + cryptography/hazmat/bindings/openssl/binding.py | 45 ++++++++++ cryptography/hazmat/bindings/openssl/crypto.py | 7 +- tests/hazmat/bindings/test_openssl.py | 106 ++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index ee82ba71..504ad551 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -44,6 +44,8 @@ class Backend(object): self._ffi = self._binding.ffi self._lib = self._binding.lib + self._binding.init_static_locks() + # adds all ciphers/digests for EVP self._lib.OpenSSL_add_all_algorithms() # registers available SSL/TLS ciphers and digests diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 1c17a5b2..cde3bdbd 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -14,6 +14,7 @@ from __future__ import absolute_import, division, print_function import sys +import threading from cryptography.hazmat.bindings.utils import build_ffi @@ -70,6 +71,10 @@ class Binding(object): "x509v3", ] + _locks = None + _lock_cb_handle = None + _lock_init_lock = threading.Lock() + ffi = None lib = None @@ -95,3 +100,43 @@ class Binding(object): def is_available(cls): # OpenSSL is the only binding so for now it must always be available return True + + @classmethod + def init_static_locks(cls): + with cls._lock_init_lock: + cls._ensure_ffi_initialized() + + if not cls._lock_cb_handle: + cls._lock_cb_handle = cls.ffi.callback( + "void(int, int, const char *, int)", + cls._lock_cb + ) + + # use Python's implementation if available + + __import__("_ssl") + + if cls.lib.CRYPTO_get_locking_callback() != cls.ffi.NULL: + return + + # otherwise setup our version + + num_locks = cls.lib.CRYPTO_num_locks() + cls._locks = [threading.Lock() for n in range(num_locks)] + + cls.lib.CRYPTO_set_locking_callback(cls._lock_cb_handle) + + @classmethod + def _lock_cb(cls, mode, n, file, line): + lock = cls._locks[n] + + if mode & cls.lib.CRYPTO_LOCK: + lock.acquire() + elif mode & cls.lib.CRYPTO_UNLOCK: + lock.release() + else: + raise RuntimeError( + "Unknown lock mode {0}: lock={1}, file={2}, line={3}".format( + mode, n, file, line + ) + ) diff --git a/cryptography/hazmat/bindings/openssl/crypto.py b/cryptography/hazmat/bindings/openssl/crypto.py index 40d91bf2..81d13b73 100644 --- a/cryptography/hazmat/bindings/openssl/crypto.py +++ b/cryptography/hazmat/bindings/openssl/crypto.py @@ -27,6 +27,11 @@ static const int CRYPTO_MEM_CHECK_ON; static const int CRYPTO_MEM_CHECK_OFF; static const int CRYPTO_MEM_CHECK_ENABLE; static const int CRYPTO_MEM_CHECK_DISABLE; +static const int CRYPTO_LOCK; +static const int CRYPTO_UNLOCK; +static const int CRYPTO_READ; +static const int CRYPTO_WRITE; +static const int CRYPTO_LOCK_SSL; """ FUNCTIONS = """ @@ -43,6 +48,7 @@ void CRYPTO_set_locking_callback(void(*)(int, int, const char *, int)); void CRYPTO_set_id_callback(unsigned long (*)(void)); unsigned long (*CRYPTO_get_id_callback(void))(void); void (*CRYPTO_get_locking_callback(void))(int, int, const char *, int); +void CRYPTO_lock(int, int, const char *, int); void OPENSSL_free(void *); """ @@ -51,7 +57,6 @@ MACROS = """ void CRYPTO_add(int *, int, int); void CRYPTO_malloc_init(void); void CRYPTO_malloc_debug_init(void); - """ CUSTOMIZATIONS = """ diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index d1e85058..b2264fb5 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -11,6 +11,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import threading +import time + +import pytest + from cryptography.hazmat.bindings.openssl.binding import Binding @@ -23,3 +28,104 @@ class TestOpenSSL(object): def test_is_available(self): assert Binding.is_available() is True + + def test_crypto_lock_init(self): + b = Binding() + b.init_static_locks() + lock_cb = b.lib.CRYPTO_get_locking_callback() + assert lock_cb != b.ffi.NULL + + def test_our_crypto_lock(self, capfd): + b = Binding() + b.init_static_locks() + + # only run this test if we are using our locking cb + original_cb = b.lib.CRYPTO_get_locking_callback() + if original_cb != b._lock_cb_handle: + pytest.skip("Not using Python locking callback implementation") + + # check that the lock state changes appropriately + lock = b._locks[b.lib.CRYPTO_LOCK_SSL] + + assert lock.acquire(False) + + lock.release() + + b.lib.CRYPTO_lock( + b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ, + b.lib.CRYPTO_LOCK_SSL, + b.ffi.NULL, + 0 + ) + + assert not lock.acquire(False) + + b.lib.CRYPTO_lock( + b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ, + b.lib.CRYPTO_LOCK_SSL, + b.ffi.NULL, + 0 + ) + + assert lock.acquire(False) + lock.release() + + # force the error path to run. + + b.lib.CRYPTO_lock( + 0, + b.lib.CRYPTO_LOCK_SSL, + b.ffi.NULL, + 0 + ) + + lock.acquire(False) + lock.release() + + out, err = capfd.readouterr() + assert "RuntimeError: Unknown lock mode" in err + + def test_crypto_lock_mutex(self): + b = Binding() + b.init_static_locks() + + # make sure whatever locking system we end up with actually acts + # like a mutex. + + self._shared_value = 0 + + def critical_loop(): + for i in range(10): + b.lib.CRYPTO_lock( + b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ, + b.lib.CRYPTO_LOCK_SSL, + b.ffi.NULL, + 0 + ) + + assert self._shared_value == 0 + self._shared_value += 1 + time.sleep(0.01) + assert self._shared_value == 1 + self._shared_value = 0 + + b.lib.CRYPTO_lock( + b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ, + b.lib.CRYPTO_LOCK_SSL, + b.ffi.NULL, + 0 + ) + + threads = [] + for x in range(10): + t = threading.Thread(target=critical_loop) + t.daemon = True + t.start() + + threads.append(t) + + while threads: + for t in threads: + t.join(0.1) + if not t.is_alive(): + threads.remove(t) -- cgit v1.2.3 From 06649cf8bcd764c51c9ee819f43a5a0a29290a38 Mon Sep 17 00:00:00 2001 From: Alex Stapleton Date: Wed, 22 Jan 2014 15:07:38 +0000 Subject: Also test the locking cb directly --- tests/hazmat/bindings/test_openssl.py | 39 ++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index b2264fb5..e5094133 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -47,43 +47,46 @@ class TestOpenSSL(object): # check that the lock state changes appropriately lock = b._locks[b.lib.CRYPTO_LOCK_SSL] + # starts out unlocked assert lock.acquire(False) - lock.release() b.lib.CRYPTO_lock( b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ, - b.lib.CRYPTO_LOCK_SSL, - b.ffi.NULL, - 0 + b.lib.CRYPTO_LOCK_SSL, b.ffi.NULL, 0 ) + # becomes locked assert not lock.acquire(False) b.lib.CRYPTO_lock( b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ, - b.lib.CRYPTO_LOCK_SSL, - b.ffi.NULL, - 0 + b.lib.CRYPTO_LOCK_SSL, b.ffi.NULL, 0 ) + # then unlocked assert lock.acquire(False) lock.release() - # force the error path to run. + # then test directly - b.lib.CRYPTO_lock( - 0, - b.lib.CRYPTO_LOCK_SSL, - b.ffi.NULL, - 0 - ) + with pytest.raises(RuntimeError): + b._lock_cb(0, b.lib.CRYPTO_LOCK_SSL, "", 1) - lock.acquire(False) + # errors shouldnt cause locking + assert lock.acquire(False) lock.release() - out, err = capfd.readouterr() - assert "RuntimeError: Unknown lock mode" in err + b._lock_cb(b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ, + b.lib.CRYPTO_LOCK_SSL, "", 1) + # locked + assert not lock.acquire(False) + + b._lock_cb(b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ, + b.lib.CRYPTO_LOCK_SSL, "", 1) + # unlocked + assert lock.acquire(False) + lock.release() def test_crypto_lock_mutex(self): b = Binding() @@ -119,9 +122,7 @@ class TestOpenSSL(object): threads = [] for x in range(10): t = threading.Thread(target=critical_loop) - t.daemon = True t.start() - threads.append(t) while threads: -- cgit v1.2.3 From fcae15014dd9510969615a7f5100704955bbeb64 Mon Sep 17 00:00:00 2001 From: Alex Stapleton Date: Thu, 23 Jan 2014 20:43:34 +0000 Subject: Some docs --- docs/hazmat/bindings/openssl.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/hazmat/bindings/openssl.rst b/docs/hazmat/bindings/openssl.rst index 373fe472..77623b22 100644 --- a/docs/hazmat/bindings/openssl.rst +++ b/docs/hazmat/bindings/openssl.rst @@ -22,6 +22,26 @@ These are `CFFI`_ bindings to the `OpenSSL`_ C library. This is a ``cffi`` library. It can be used to call OpenSSL functions, and access constants. + .. classmethod:: init_static_locks + + Enables the best available locking callback for OpenSSL. + See :ref:`openssl-threading`. + +.. _openssl-threading: + +Threading +--------- + +`cryptography` enables OpenSSLs `thread safety facilities`_ in two different +ways depending on the configuration of your system. Normally the locking +callbacks provided by your Python implementation specifically for OpenSSL will +be used. However if you have linked `cryptography` to a different version of +OpenSSL than that used by your Python implementation we enable an alternative +locking callback. This version is implemented in Python and so may result in +lower performance in some situations. In particular parallelism is reduced +because it has to acquire the GIL whenever any lock operations occur within +OpenSSL. .. _`CFFI`: https://cffi.readthedocs.org/ .. _`OpenSSL`: https://www.openssl.org/ +.. _`thread safety facilities`: http://www.openssl.org/docs/crypto/threads.html -- cgit v1.2.3 From 7ce51f26bb10dc56483a5f0e9639092ccb2f9d5c Mon Sep 17 00:00:00 2001 From: Alex Stapleton Date: Thu, 23 Jan 2014 20:43:49 +0000 Subject: Split a test in half --- tests/hazmat/bindings/test_openssl.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index e5094133..43a07760 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -35,14 +35,20 @@ class TestOpenSSL(object): lock_cb = b.lib.CRYPTO_get_locking_callback() assert lock_cb != b.ffi.NULL - def test_our_crypto_lock(self, capfd): - b = Binding() - b.init_static_locks() - + def _skip_if_not_fallback_lock(self, b): # only run this test if we are using our locking cb original_cb = b.lib.CRYPTO_get_locking_callback() if original_cb != b._lock_cb_handle: - pytest.skip("Not using Python locking callback implementation") + pytest.skip( + "Not using the fallback Python locking callback " + "implementation. Probably because import _ssl set one" + ) + + def test_fallback_crypto_lock_via_openssl_api(self): + b = Binding() + b.init_static_locks() + + self._skip_if_not_fallback_lock(b) # check that the lock state changes appropriately lock = b._locks[b.lib.CRYPTO_LOCK_SSL] @@ -68,7 +74,13 @@ class TestOpenSSL(object): assert lock.acquire(False) lock.release() - # then test directly + def test_fallback_crypto_lock_via_binding_api(self): + b = Binding() + b.init_static_locks() + + self._skip_if_not_fallback_lock(b) + + lock = b._locks[b.lib.CRYPTO_LOCK_SSL] with pytest.raises(RuntimeError): b._lock_cb(0, b.lib.CRYPTO_LOCK_SSL, "", 1) -- cgit v1.2.3 From 57e280b0f87096238b5cf5590e1157d433f6f049 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 00:26:07 -0600 Subject: add doc info about how to link against homebrew OpenSSL on OS X --- docs/hazmat/backends/openssl.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst index a1f2d28a..355a1ebb 100644 --- a/docs/hazmat/backends/openssl.rst +++ b/docs/hazmat/backends/openssl.rst @@ -40,4 +40,15 @@ You'll also need to generate your own ``openssl.ld`` file. For example:: You should replace the version string on the first line as appropriate for your build. +Using your own OpenSSL on OS X +------------------------------ + +To link cryptography against a custom version of OpenSSL you'll need to set +``ARCHFLAGS``, ``LDFLAGS``, and ``CFLAGS``. + +An example using a `Homebrew`_ OpenSSL installation:: + + ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography + .. _`OpenSSL`: https://www.openssl.org/ +.. _`Homebrew`: http://brew.sh -- cgit v1.2.3 From 8caeb8ddde096686225f476146134e2ec65190b6 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 12:38:14 -0600 Subject: add env so homebrew instructions work in fish as well as bash on OS X --- docs/hazmat/backends/openssl.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst index 355a1ebb..a2417cf2 100644 --- a/docs/hazmat/backends/openssl.rst +++ b/docs/hazmat/backends/openssl.rst @@ -48,7 +48,8 @@ To link cryptography against a custom version of OpenSSL you'll need to set An example using a `Homebrew`_ OpenSSL installation:: - ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography + env ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography + .. _`OpenSSL`: https://www.openssl.org/ .. _`Homebrew`: http://brew.sh -- cgit v1.2.3 From 017f0b05ebe6026b0ad0d6205fca6ec1e60540b4 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 24 Jan 2014 10:20:36 -0600 Subject: add instructions on how to install openssl via homebrew --- docs/hazmat/backends/openssl.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst index a2417cf2..d305520a 100644 --- a/docs/hazmat/backends/openssl.rst +++ b/docs/hazmat/backends/openssl.rst @@ -44,9 +44,12 @@ Using your own OpenSSL on OS X ------------------------------ To link cryptography against a custom version of OpenSSL you'll need to set -``ARCHFLAGS``, ``LDFLAGS``, and ``CFLAGS``. +``ARCHFLAGS``, ``LDFLAGS``, and ``CFLAGS``. OpenSSL can be installed via +`Homebrew`_:: -An example using a `Homebrew`_ OpenSSL installation:: + brew install openssl + +Then install cryptography linking against the brewed version:: env ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography -- cgit v1.2.3 From 917e82e2da0091e90307b20cfa8feaf2fb211ada Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 24 Jan 2014 11:34:04 -0600 Subject: Mark that tehse are console sections so they syntax highlight --- docs/hazmat/backends/openssl.rst | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst index d305520a..926ec7d1 100644 --- a/docs/hazmat/backends/openssl.rst +++ b/docs/hazmat/backends/openssl.rst @@ -26,9 +26,11 @@ The options you need to add allow the linker to identify every symbol correctly even when multiple versions of the library are linked into the same program. If you are using your distribution's source packages these will probably be patched in for you already, otherwise you'll need to use options something like -this when configuring OpenSSL:: +this when configuring OpenSSL: - ./config -Wl,--version-script=openssl.ld -Wl,-Bsymbolic-functions -fPIC shared +.. code-block:: console + + $ ./config -Wl,--version-script=openssl.ld -Wl,-Bsymbolic-functions -fPIC shared You'll also need to generate your own ``openssl.ld`` file. For example:: @@ -45,13 +47,17 @@ Using your own OpenSSL on OS X To link cryptography against a custom version of OpenSSL you'll need to set ``ARCHFLAGS``, ``LDFLAGS``, and ``CFLAGS``. OpenSSL can be installed via -`Homebrew`_:: +`Homebrew`_: + +.. code-block:: console + + $ brew install openssl - brew install openssl +Then install cryptography linking against the brewed version: -Then install cryptography linking against the brewed version:: +.. code-block:: console - env ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography + $ env ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography .. _`OpenSSL`: https://www.openssl.org/ -- cgit v1.2.3 From 0d62fb05a9203398510b2f0a6c940daab640f1d4 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 24 Jan 2014 12:19:05 -0600 Subject: Switch to using our own InternalError, instead of SystemError --- cryptography/exceptions.py | 4 ++++ cryptography/hazmat/backends/commoncrypto/backend.py | 9 ++++++--- cryptography/hazmat/backends/openssl/backend.py | 6 ++++-- tests/hazmat/backends/test_commoncrypto.py | 6 +++--- tests/hazmat/backends/test_openssl.py | 10 +++++----- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py index 44363c24..2654b453 100644 --- a/cryptography/exceptions.py +++ b/cryptography/exceptions.py @@ -34,3 +34,7 @@ class InvalidTag(Exception): class InvalidSignature(Exception): pass + + +class InternalError(Exception): + pass diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 7193f1d8..ebb834cb 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -16,7 +16,9 @@ from __future__ import absolute_import, division, print_function from collections import namedtuple from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm, InvalidTag +from cryptography.exceptions import ( + UnsupportedAlgorithm, InvalidTag, InternalError +) from cryptography.hazmat.backends.interfaces import ( HashBackend, HMACBackend, CipherBackend ) @@ -197,8 +199,9 @@ class Backend(object): "the block length" ) else: - raise SystemError( - "The backend returned an error. Code: {0}".format(response) + raise InternalError( + "The backend returned an unkown error, consider filing a bug. " + "Code: {0}.".format(response) ) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index ee82ba71..b5116be4 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -16,7 +16,9 @@ from __future__ import absolute_import, division, print_function import itertools from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm, InvalidTag +from cryptography.exceptions import ( + UnsupportedAlgorithm, InvalidTag, InternalError +) from cryptography.hazmat.backends.interfaces import ( CipherBackend, HashBackend, HMACBackend ) @@ -154,7 +156,7 @@ class Backend(object): "the block length" ) - raise SystemError( + raise InternalError( "Unknown error code from OpenSSL, you should probably file a bug." ) diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py index cfa332d0..7cc0f72f 100644 --- a/tests/hazmat/backends/test_commoncrypto.py +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -14,7 +14,7 @@ import pytest from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.exceptions import UnsupportedAlgorithm, InternalError from cryptography.hazmat.bindings.commoncrypto.binding import Binding from cryptography.hazmat.primitives import interfaces from cryptography.hazmat.primitives.ciphers.algorithms import AES @@ -49,10 +49,10 @@ class TestCommonCrypto(object): with pytest.raises(ValueError): backend._check_response(backend._lib.kCCAlignmentError) - with pytest.raises(SystemError): + with pytest.raises(InternalError): backend._check_response(backend._lib.kCCMemoryFailure) - with pytest.raises(SystemError): + with pytest.raises(InternalError): backend._check_response(backend._lib.kCCDecodeError) def test_nonexistent_aead_cipher(self): diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 2a329920..f01c3f64 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -14,7 +14,7 @@ import pytest from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.exceptions import UnsupportedAlgorithm, InternalError from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends.openssl.backend import backend, Backend from cryptography.hazmat.primitives import interfaces @@ -76,20 +76,20 @@ class TestOpenSSL(object): cipher.encryptor() def test_handle_unknown_error(self): - with pytest.raises(SystemError): + with pytest.raises(InternalError): backend._handle_error_code(0, 0, 0) - with pytest.raises(SystemError): + with pytest.raises(InternalError): backend._handle_error_code(backend._lib.ERR_LIB_EVP, 0, 0) - with pytest.raises(SystemError): + with pytest.raises(InternalError): backend._handle_error_code( backend._lib.ERR_LIB_EVP, backend._lib.EVP_F_EVP_ENCRYPTFINAL_EX, 0 ) - with pytest.raises(SystemError): + with pytest.raises(InternalError): backend._handle_error_code( backend._lib.ERR_LIB_EVP, backend._lib.EVP_F_EVP_DECRYPTFINAL_EX, -- cgit v1.2.3 From 81744df9e4a109e33743825be6493cffe3026e09 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 24 Jan 2014 12:25:23 -0600 Subject: Typo fix --- cryptography/hazmat/backends/commoncrypto/backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index ebb834cb..4e70cab5 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -200,8 +200,8 @@ class Backend(object): ) else: raise InternalError( - "The backend returned an unkown error, consider filing a bug. " - "Code: {0}.".format(response) + "The backend returned an unknown error, consider filing a bug." + " Code: {0}.".format(response) ) -- cgit v1.2.3 From 5fb7eb442d56642f201ba016fa5e8f99943f0bfe Mon Sep 17 00:00:00 2001 From: Alex Stapleton Date: Fri, 24 Jan 2014 19:02:33 +0000 Subject: Backticks --- docs/hazmat/bindings/openssl.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/bindings/openssl.rst b/docs/hazmat/bindings/openssl.rst index 77623b22..557f8c4d 100644 --- a/docs/hazmat/bindings/openssl.rst +++ b/docs/hazmat/bindings/openssl.rst @@ -32,10 +32,10 @@ These are `CFFI`_ bindings to the `OpenSSL`_ C library. Threading --------- -`cryptography` enables OpenSSLs `thread safety facilities`_ in two different +``cryptography`` enables OpenSSLs `thread safety facilities`_ in two different ways depending on the configuration of your system. Normally the locking callbacks provided by your Python implementation specifically for OpenSSL will -be used. However if you have linked `cryptography` to a different version of +be used. However if you have linked ``cryptography`` to a different version of OpenSSL than that used by your Python implementation we enable an alternative locking callback. This version is implemented in Python and so may result in lower performance in some situations. In particular parallelism is reduced -- cgit v1.2.3 From 29446cd8315985680fd2af0d0137c3d1c4c2a4a1 Mon Sep 17 00:00:00 2001 From: Alex Stapleton Date: Fri, 24 Jan 2014 19:04:01 +0000 Subject: Remove the contenious test of contention --- tests/hazmat/bindings/test_openssl.py | 46 ----------------------------------- 1 file changed, 46 deletions(-) diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index 43a07760..35eb7e8d 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -11,9 +11,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import threading -import time - import pytest from cryptography.hazmat.bindings.openssl.binding import Binding @@ -99,46 +96,3 @@ class TestOpenSSL(object): # unlocked assert lock.acquire(False) lock.release() - - def test_crypto_lock_mutex(self): - b = Binding() - b.init_static_locks() - - # make sure whatever locking system we end up with actually acts - # like a mutex. - - self._shared_value = 0 - - def critical_loop(): - for i in range(10): - b.lib.CRYPTO_lock( - b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ, - b.lib.CRYPTO_LOCK_SSL, - b.ffi.NULL, - 0 - ) - - assert self._shared_value == 0 - self._shared_value += 1 - time.sleep(0.01) - assert self._shared_value == 1 - self._shared_value = 0 - - b.lib.CRYPTO_lock( - b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ, - b.lib.CRYPTO_LOCK_SSL, - b.ffi.NULL, - 0 - ) - - threads = [] - for x in range(10): - t = threading.Thread(target=critical_loop) - t.start() - threads.append(t) - - while threads: - for t in threads: - t.join(0.1) - if not t.is_alive(): - threads.remove(t) -- cgit v1.2.3 From 7ecb98503ddb1e580b6f8b1f33053d64b59bbba4 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 24 Jan 2014 15:17:47 -0600 Subject: Added thread safety to the changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index c8843821..545d6945 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,7 @@ Changelog * Removed ``register_cipher_adapter`` method from :class:`~cryptography.hazmat.backends.interfaces.CipherBackend`. * Added support for the OpenSSL backend under Windows. +* Improved thread-safety for the OpenSSL backend. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From ac423232f884b94a24257e80ac95cccd5749ba9f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 25 Jan 2014 14:13:09 -0600 Subject: RSA private/public key interface + docs --- cryptography/hazmat/primitives/interfaces.py | 88 +++++++++++++++++++++++ docs/hazmat/primitives/interfaces.rst | 100 +++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 7a6bf3e2..81bab9d7 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -169,3 +169,91 @@ class HashContext(six.with_metaclass(abc.ABCMeta)): """ Return a HashContext that is a copy of the current context. """ + + +class RSAPrivateKey(six.with_metaclass(abc.ABCMeta)): + @abc.abstractproperty + def modulus(self): + """ + The public modulus of the RSA key. Alias for n. + """ + + @abc.abstractproperty + def public_exponent(self): + """ + The public exponent of the RSA key. Alias for e. + """ + + @abc.abstractproperty + def key_length(self): + """ + The bit length of the public modulus. + """ + + @abc.abstractproperty + def public_key(self): + """ + The RSAPublicKey associated with this private key. + """ + + @abc.abstractproperty + def n(self): + """ + The public modulus of the RSA key. + """ + + @abc.abstractproperty + def p(self): + """ + One of the two primes used to generate d. + """ + + @abc.abstractproperty + def q(self): + """ + One of the two primes used to generate d. + """ + + @abc.abstractproperty + def d(self): + """ + The private exponent. This can be calculated using p and q. + """ + + @abc.abstractproperty + def e(self): + """ + The public exponent of the RSA key. + """ + + +class RSAPublicKey(six.with_metaclass(abc.ABCMeta)): + @abc.abstractproperty + def modulus(self): + """ + The public modulus of the RSA key. Alias for n. + """ + + @abc.abstractproperty + def public_exponent(self): + """ + The public exponent of the RSA key. Alias for e. + """ + + @abc.abstractproperty + def key_length(self): + """ + The bit length of the public modulus. + """ + + @abc.abstractproperty + def n(self): + """ + The public modulus of the RSA key. + """ + + @abc.abstractproperty + def e(self): + """ + The public exponent of the RSA key. + """ diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index edb24cd9..40b49a54 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -102,3 +102,103 @@ Interfaces used by the symmetric cipher modes described in Exact requirements of the nonce are described by the documentation of individual modes. + +Asymmetric Interfaces +~~~~~~~~~~~~~~~~~~~~~ + +.. class:: RSAPrivateKey + + An `RSA`_ private key. + + .. attribute:: public_key + + :type: :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey` + + An RSA public key object corresponding to the values of the private key. + + .. attribute:: modulus + + :type: str + + Hexadecimal representation of the public modulus. Alias for ``n``. + + .. attribute:: public_exponent + + :type: int + + The public exponent. Alias for ``e``. + + .. attribute:: key_length + + :type: int + + The bit length of the modulus. + + .. attribute:: p + + :type: str + + Hexadecimal representation of ``p``, one of the two primes composing + ``n``. + + .. attribute:: q + + :type: str + + Hexadecimal representation of ``q``, one of the two primes composing + ``n``. + + .. attribute:: d + + :type: str + + Hexadecimal representation of ``d``, the private exponent. + + .. attribute:: n + + :type: str + + Hexadecimal representation of the public modulus. + + .. attribute:: e + + :type: int + + The public exponent. + + +.. class:: RSAPublicKey + + An `RSA`_ public key. + + .. attribute:: modulus + + :type: str + + Hexadecimal representation of the public modulus. Alias for ``n``. + + .. attribute:: key_length + + :type: int + + The bit length of the modulus. + + .. attribute:: public_exponent + + :type: int + + The public exponent. Alias for ``e``. + + .. attribute:: n + + :type: str + + Hexadecimal representation of the public modulus. + + .. attribute:: e + + :type: int + + The public exponent. + +.. _`RSA`: http://en.wikipedia.org/wiki/RSA_(cryptosystem) -- cgit v1.2.3 From d527b30d2f4515ea8d2c9af7fe7317dcab276aff Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 26 Jan 2014 11:41:38 -0600 Subject: update interface docs to make attributes return int (per irc discussion) --- docs/hazmat/primitives/interfaces.rst | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 40b49a54..c2d4c53d 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -118,9 +118,9 @@ Asymmetric Interfaces .. attribute:: modulus - :type: str + :type: int - Hexadecimal representation of the public modulus. Alias for ``n``. + The public modulus. Alias for ``n``. .. attribute:: public_exponent @@ -136,29 +136,27 @@ Asymmetric Interfaces .. attribute:: p - :type: str + :type: int - Hexadecimal representation of ``p``, one of the two primes composing - ``n``. + ``p``, one of the two primes composing ``n``. .. attribute:: q - :type: str + :type: int - Hexadecimal representation of ``q``, one of the two primes composing - ``n``. + ``q``, one of the two primes composing ``n``. .. attribute:: d - :type: str + :type: int - Hexadecimal representation of ``d``, the private exponent. + The private exponent. .. attribute:: n - :type: str + :type: int - Hexadecimal representation of the public modulus. + The public modulus. .. attribute:: e @@ -173,9 +171,9 @@ Asymmetric Interfaces .. attribute:: modulus - :type: str + :type: int - Hexadecimal representation of the public modulus. Alias for ``n``. + The public modulus. Alias for ``n``. .. attribute:: key_length @@ -191,9 +189,9 @@ Asymmetric Interfaces .. attribute:: n - :type: str + :type: int - Hexadecimal representation of the public modulus. + The public modulus. .. attribute:: e -- cgit v1.2.3 From 0e94fbe27e3942620906b7f275fa69c55defacd5 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 26 Jan 2014 11:47:21 -0600 Subject: make public_key an abstractmethod, update docs --- cryptography/hazmat/primitives/interfaces.py | 2 +- docs/hazmat/primitives/interfaces.rst | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 81bab9d7..c91fd5c5 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -190,7 +190,7 @@ class RSAPrivateKey(six.with_metaclass(abc.ABCMeta)): The bit length of the public modulus. """ - @abc.abstractproperty + @abc.abstractmethod def public_key(self): """ The RSAPublicKey associated with this private key. diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index c2d4c53d..02ba10e2 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -110,7 +110,7 @@ Asymmetric Interfaces An `RSA`_ private key. - .. attribute:: public_key + .. method:: public_key() :type: :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey` @@ -120,13 +120,13 @@ Asymmetric Interfaces :type: int - The public modulus. Alias for ``n``. + The public modulus. .. attribute:: public_exponent :type: int - The public exponent. Alias for ``e``. + The public exponent. .. attribute:: key_length @@ -156,13 +156,13 @@ Asymmetric Interfaces :type: int - The public modulus. + The public modulus. Alias for ``modulus``. .. attribute:: e :type: int - The public exponent. + The public exponent. Alias for ``public_exponent``. .. class:: RSAPublicKey @@ -173,7 +173,7 @@ Asymmetric Interfaces :type: int - The public modulus. Alias for ``n``. + The public modulus. .. attribute:: key_length @@ -185,18 +185,18 @@ Asymmetric Interfaces :type: int - The public exponent. Alias for ``e``. + The public exponent. .. attribute:: n :type: int - The public modulus. + The public modulus. Alias for ``modulus``. .. attribute:: e :type: int - The public exponent. + The public exponent. Alias for ``public_exponent``. .. _`RSA`: http://en.wikipedia.org/wiki/RSA_(cryptosystem) -- cgit v1.2.3 From 6cf35f39677f04f5711ae062161858568cc14bcd Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 26 Jan 2014 11:52:17 -0600 Subject: update docstrings --- cryptography/hazmat/primitives/interfaces.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index c91fd5c5..293fcd78 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -175,13 +175,13 @@ class RSAPrivateKey(six.with_metaclass(abc.ABCMeta)): @abc.abstractproperty def modulus(self): """ - The public modulus of the RSA key. Alias for n. + The public modulus of the RSA key. """ @abc.abstractproperty def public_exponent(self): """ - The public exponent of the RSA key. Alias for e. + The public exponent of the RSA key. """ @abc.abstractproperty @@ -199,7 +199,7 @@ class RSAPrivateKey(six.with_metaclass(abc.ABCMeta)): @abc.abstractproperty def n(self): """ - The public modulus of the RSA key. + The public modulus of the RSA key. Alias for modulus. """ @abc.abstractproperty @@ -223,7 +223,7 @@ class RSAPrivateKey(six.with_metaclass(abc.ABCMeta)): @abc.abstractproperty def e(self): """ - The public exponent of the RSA key. + The public exponent of the RSA key. Alias for public_exponent. """ @@ -231,13 +231,13 @@ class RSAPublicKey(six.with_metaclass(abc.ABCMeta)): @abc.abstractproperty def modulus(self): """ - The public modulus of the RSA key. Alias for n. + The public modulus of the RSA key. """ @abc.abstractproperty def public_exponent(self): """ - The public exponent of the RSA key. Alias for e. + The public exponent of the RSA key. """ @abc.abstractproperty @@ -249,11 +249,11 @@ class RSAPublicKey(six.with_metaclass(abc.ABCMeta)): @abc.abstractproperty def n(self): """ - The public modulus of the RSA key. + The public modulus of the RSA key. Alias for modulus. """ @abc.abstractproperty def e(self): """ - The public exponent of the RSA key. + The public exponent of the RSA key. Alias for public_exponent. """ -- cgit v1.2.3 From 359b94631c3380e2f86cf1f8e95090f7a350438f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 26 Jan 2014 12:03:05 -0600 Subject: methods don't have a type, they return things --- docs/hazmat/primitives/interfaces.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 02ba10e2..57a84120 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -112,7 +112,7 @@ Asymmetric Interfaces .. method:: public_key() - :type: :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey` + :return: :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey` An RSA public key object corresponding to the values of the private key. -- cgit v1.2.3 From 82629f4adc8bfc8899b2b99915fad61b95e17fe6 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 26 Jan 2014 12:25:02 -0600 Subject: adding versionadded --- docs/hazmat/primitives/interfaces.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 57a84120..0c193bd5 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -108,6 +108,8 @@ Asymmetric Interfaces .. class:: RSAPrivateKey +.. versionadded:: 0.2 + An `RSA`_ private key. .. method:: public_key() @@ -167,6 +169,8 @@ Asymmetric Interfaces .. class:: RSAPublicKey +.. versionadded:: 0.2 + An `RSA`_ public key. .. attribute:: modulus -- cgit v1.2.3 From 46688b11ed7b4f8c53a61feb1bf355a38d3e939c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 26 Jan 2014 13:23:13 -0600 Subject: indentation is fun --- docs/hazmat/primitives/interfaces.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 0c193bd5..4739680a 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -108,7 +108,7 @@ Asymmetric Interfaces .. class:: RSAPrivateKey -.. versionadded:: 0.2 + .. versionadded:: 0.2 An `RSA`_ private key. @@ -169,7 +169,7 @@ Asymmetric Interfaces .. class:: RSAPublicKey -.. versionadded:: 0.2 + .. versionadded:: 0.2 An `RSA`_ public key. -- cgit v1.2.3 From 2a9187492c105636b92a58e9b470a8f5db6e4e81 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 26 Jan 2014 16:53:44 -0600 Subject: Linkify the RSA docs --- docs/hazmat/primitives/interfaces.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 4739680a..bf78e367 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -140,13 +140,13 @@ Asymmetric Interfaces :type: int - ``p``, one of the two primes composing ``n``. + ``p``, one of the two primes composing the :attr:`modulus`. .. attribute:: q :type: int - ``q``, one of the two primes composing ``n``. + ``q``, one of the two primes composing the :attr:`modulus`. .. attribute:: d @@ -158,13 +158,13 @@ Asymmetric Interfaces :type: int - The public modulus. Alias for ``modulus``. + The public modulus. Alias for :attr:`modulus`. .. attribute:: e :type: int - The public exponent. Alias for ``public_exponent``. + The public exponent. Alias for :attr:`public_exponent`. .. class:: RSAPublicKey @@ -195,12 +195,13 @@ Asymmetric Interfaces :type: int - The public modulus. Alias for ``modulus``. + The public modulus. Alias for :attr:`modulus`. .. attribute:: e :type: int - The public exponent. Alias for ``public_exponent``. + The public exponent. Alias for :attr:`public_exponent`. + .. _`RSA`: http://en.wikipedia.org/wiki/RSA_(cryptosystem) -- cgit v1.2.3 From 36e651c00aaa8f9016aed73bf30061fbd1bdf6e7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 27 Jan 2014 10:08:35 -0800 Subject: Represent the hash vectors more cleanly --- tests/hazmat/primitives/utils.py | 7 ++----- tests/utils.py | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 6ecc70ff..f27afe41 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -141,8 +141,7 @@ def generate_hash_test(param_loader, path, file_names, hash_cls): def hash_test(backend, algorithm, params): - msg = params[0] - md = params[1] + msg, md = params m = hashes.Hash(algorithm, backend=backend) m.update(binascii.unhexlify(msg)) expected_md = md.replace(" ", "").lower().encode("ascii") @@ -206,9 +205,7 @@ def generate_hmac_test(param_loader, path, file_names, algorithm): def hmac_test(backend, algorithm, params): - msg = params[0] - md = params[1] - key = params[2] + msg, md, key = params h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend) h.update(binascii.unhexlify(msg)) assert h.finalize() == binascii.unhexlify(md.encode("ascii")) diff --git a/tests/utils.py b/tests/utils.py index a2432256..507bc421 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -11,11 +11,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +import collections import os import pytest +HashVector = collections.namedtuple("HashVector", ["message", "digest"]) +KeyedHashVector = collections.namedtuple( + "KeyedHashVector", ["message", "digest", "key"] +) + + def select_backends(names, backend_list): if names is None: return backend_list @@ -158,27 +165,23 @@ def load_hash_vectors(vector_data): if line.startswith("Len"): length = int(line.split(" = ")[1]) elif line.startswith("Key"): - """ - HMAC vectors contain a key attribute. Hash vectors do not. - """ + # HMAC vectors contain a key attribute. Hash vectors do not. key = line.split(" = ")[1].encode("ascii") elif line.startswith("Msg"): - """ - In the NIST vectors they have chosen to represent an empty - string as hex 00, which is of course not actually an empty - string. So we parse the provided length and catch this edge case. - """ + # In the NIST vectors they have chosen to represent an empty + # string as hex 00, which is of course not actually an empty + # string. So we parse the provided length and catch this edge case. msg = line.split(" = ")[1].encode("ascii") if length > 0 else b"" elif line.startswith("MD"): md = line.split(" = ")[1] # after MD is found the Msg+MD (+ potential key) tuple is complete if key is not None: - vectors.append((msg, md, key)) + vectors.append(KeyedHashVector(msg, md, key)) key = None msg = None md = None else: - vectors.append((msg, md)) + vectors.append(HashVector(msg, md)) msg = None md = None else: -- cgit v1.2.3 From b2774f53bc5840ae7c414ee78bef654a2ae89f01 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 27 Jan 2014 11:05:29 -0800 Subject: Begin designing the KDF interfaces. Fixes #511 --- cryptography/exceptions.py | 4 ++++ cryptography/hazmat/primitives/interfaces.py | 16 +++++++++++++++ docs/exceptions.rst | 6 ++++++ docs/hazmat/primitives/interfaces.rst | 30 ++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py index 2654b453..e2542a1f 100644 --- a/cryptography/exceptions.py +++ b/cryptography/exceptions.py @@ -38,3 +38,7 @@ class InvalidSignature(Exception): class InternalError(Exception): pass + + +class InvalidKey(Exception): + pass diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 293fcd78..1a27644f 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -257,3 +257,19 @@ class RSAPublicKey(six.with_metaclass(abc.ABCMeta)): """ The public exponent of the RSA key. Alias for public_exponent. """ + + +class KeyDerivationFunction(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def derive(self, key_material): + """ + Deterministically generates and returns a new key based on the existing + key material. + """ + + @abc.abstractmethod + def verify(self, key_material, expected_key): + """ + Checks whether the key generated by the key material matches the + expected derived key. Raises an exception if they do not match. + """ diff --git a/docs/exceptions.rst b/docs/exceptions.rst index 1fbd3267..f9e29f3c 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -30,3 +30,9 @@ Exceptions This is raised when a backend doesn't support the requested algorithm (or combination of algorithms). + + +.. class:: InvalidKey + + This is raised when the verify method of a key derivation function does not + compare equal. diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index bf78e367..ac48dd2c 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -204,4 +204,34 @@ Asymmetric Interfaces The public exponent. Alias for :attr:`public_exponent`. +Key Derivation Functions +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: KeyDerivationFunction + + .. method:: derive(key_material) + + :param key_material bytes: The raw key material. Depending on what key + derivation function you are using this could + be either random material, or a user + supplied password. + :return: The resulting key. + + The generates and returns a new key from the supplied key material. + + .. method:: verify(key_material, expected_key) + + :param key_material bytes: The raw key material. This is the same as + ``key_material`` in :meth:`derive`. + :param expected_key bytes: What the expected result of deriving a new + key is. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + + This checks whether deriving a key from the supplied ``key_material`` + generates the same key as the ``expected_key``, and raises an exception + if they do not match. This can be used for something like checking + whether a user's password attempt matches the stored derived key. + .. _`RSA`: http://en.wikipedia.org/wiki/RSA_(cryptosystem) -- cgit v1.2.3 From 8412e7414d7f6246a2dccf478f3884480bba6c47 Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 27 Jan 2014 11:28:09 -0800 Subject: Convert the HKDF vectors to the NIST format. --- tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt | 8 ++++++++ tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt index 598ecaae..b3fd03aa 100644 --- a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt +++ b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt @@ -1,6 +1,8 @@ # A.4. Test Case 4 # Basic test case with SHA-1 +COUNT = 4 + Hash = SHA-1 IKM = 0b0b0b0b0b0b0b0b0b0b0b salt = 000102030405060708090a0b0c @@ -13,6 +15,8 @@ OKM = 085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e42247 # A.5. Test Case 5 # Test with SHA-1 and longer inputs/outputs +COUNT = 5 + Hash = SHA-1 IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f salt = 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf @@ -25,6 +29,8 @@ OKM = 0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e # A.6. Test Case 6 # Test with SHA-1 and zero-length salt/info +COUNT = 6 + Hash = SHA-1 IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b salt = @@ -38,6 +44,8 @@ OKM = 0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de # Test with SHA-1, salt not provided (defaults to HashLen zero octets), # zero-length info +COUNT = 7 + Hash = SHA-1 IKM = 0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c salt = diff --git a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt index 170dd13e..9068a739 100644 --- a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt +++ b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt @@ -1,6 +1,8 @@ # A.1. Test Case 1 # Basic test case with SHA-256 +COUNT = 1 + Hash = SHA-256 IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b salt = 000102030405060708090a0b0c @@ -12,6 +14,8 @@ OKM = 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d # A.2. Test Case 2 # Test with SHA-256 and longer inputs/outputs +COUNT = 2 + Hash = SHA-256 IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f salt = 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf @@ -24,6 +28,8 @@ OKM = b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99c # A.3. Test Case 3 # Test with SHA-256 and zero-length salt/info +COUNT = 3 + Hash = SHA-256 IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b salt = -- cgit v1.2.3 From 288403a1a45554609d8b519d8b8d5a5abc9576c5 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 27 Jan 2014 15:59:17 -0800 Subject: Attempt some more natural language --- docs/exceptions.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/exceptions.rst b/docs/exceptions.rst index f9e29f3c..1e31e31c 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -10,8 +10,8 @@ Exceptions .. class:: InvalidSignature - This is raised when the verify method of a hash context does not - compare equal. + This is raised when the verify method of a hash context's computed digest + does not match the expected digest. .. class:: NotYetFinalized @@ -34,5 +34,5 @@ Exceptions .. class:: InvalidKey - This is raised when the verify method of a key derivation function does not - compare equal. + This is raised when the verify method of a key derivation function's + computed key does not match the expected key. -- cgit v1.2.3 From 24eb677117e79322fb07c2c807eef7bf2996828f Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 27 Jan 2014 16:15:54 -0800 Subject: DOcument that verify() apis should be provided --- docs/contributing.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/contributing.rst b/docs/contributing.rst index 3de41fd5..f4bc769c 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -60,6 +60,12 @@ always indistinguishable. As a result ``cryptography`` has, as a design philosophy: "make it hard to do insecure things". Here are a few strategies for API design which should be both followed, and should inspire other API choices: +If a user will need to compare a user provided value with a computed value (for +example, checking a signature on something), there should be an API provided +which performs the check for the user in a secure way (for example, using a +constant time comparison), rather than requiring the user to perform the +comparison themselves. + If it is incorrect to ignore the result of a method, it should raise an exception, and not return a boolean ``True``/``False`` flag. For example, a method to verify a signature should raise ``InvalidSignature``, and not return -- cgit v1.2.3 From 61137bcd771f5e7e9f2f11625a75d15cb32b2816 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 27 Jan 2014 16:37:04 -0800 Subject: Reword for clarity --- docs/contributing.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index f4bc769c..184ba214 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -60,11 +60,11 @@ always indistinguishable. As a result ``cryptography`` has, as a design philosophy: "make it hard to do insecure things". Here are a few strategies for API design which should be both followed, and should inspire other API choices: -If a user will need to compare a user provided value with a computed value (for -example, checking a signature on something), there should be an API provided -which performs the check for the user in a secure way (for example, using a -constant time comparison), rather than requiring the user to perform the -comparison themselves. +If it is necessary to compare a user provided value with a computed value (for +example, verifying a signature), there should be an API provided which performs +the verification in a secure way (for example, using a constant time +comparison), rather than requiring the user to perform the comparison +themselves. If it is incorrect to ignore the result of a method, it should raise an exception, and not return a boolean ``True``/``False`` flag. For example, a -- cgit v1.2.3 From 44fd0747de556c60853d2c522705501f19102c4f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 27 Jan 2014 21:04:44 -0600 Subject: add PBKDF2 SHA1 vectors from RFC 6070 --- .../vectors/KDF/rfc-6070-PBKDF2-SHA1.txt | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/hazmat/primitives/vectors/KDF/rfc-6070-PBKDF2-SHA1.txt diff --git a/tests/hazmat/primitives/vectors/KDF/rfc-6070-PBKDF2-SHA1.txt b/tests/hazmat/primitives/vectors/KDF/rfc-6070-PBKDF2-SHA1.txt new file mode 100644 index 00000000..739f3f36 --- /dev/null +++ b/tests/hazmat/primitives/vectors/KDF/rfc-6070-PBKDF2-SHA1.txt @@ -0,0 +1,48 @@ +# PBKDF2 SHA1 vectors from http://www.ietf.org/rfc/rfc6070.txt + +COUNT = 0 +PASSWORD = password +SALT = salt +ITERATIONS = 1 +LENGTH = 20 +DERIVED_KEY = 0c60c80f961f0e71f3a9b524af6012062fe037a6 + + +COUNT = 1 +PASSWORD = password +SALT = salt +ITERATIONS = 2 +LENGTH = 20 +DERIVED_KEY = ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957 + + +COUNT = 2 +PASSWORD = password +SALT = salt +ITERATIONS = 4096 +LENGTH = 20 +DERIVED_KEY = 4b007901b765489abead49d926f721d065a429c1 + + +COUNT = 3 +PASSWORD = password +SALT = salt +ITERATIONS = 16777216 +LENGTH = 20 +DERIVED_KEY = eefe3d61cd4da4e4e9945b3d6ba2158c2634e984 + + +COUNT = 4 +PASSWORD = passwordPASSWORDpassword +SALT = saltSALTsaltSALTsaltSALTsaltSALTsalt +ITERATIONS = 4096 +LENGTH = 25 +DERIVED_KEY = 3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038 + + +COUNT = 5 +PASSWORD = pass\0word +SALT = sa\0lt +ITERATIONS = 4096 +LENGTH = 16 +DERIVED_KEY = 56fa6aa75548099dcc37d7f03425e0c3 -- cgit v1.2.3 From 7010df99d7096b8a0555752c51a1bd1e587c28dc Mon Sep 17 00:00:00 2001 From: Konstantinos Koukopoulos Date: Tue, 28 Jan 2014 03:35:25 -0800 Subject: document how to build w/ custom openssl on windows --- docs/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index a1cebc30..255c9e96 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,7 +17,8 @@ You can install ``cryptography`` with ``pip``: .. note:: If you're on Windows you'll need to make sure you have OpenSSL installed. - There are `pre-compiled binaries`_ available. + There are `pre-compiled binaries`_ available. If your installation is in + an unusual location ``set LIB=C:\OpenSSL-x.y.z\lib;%LIB%`` before installing. Why a new crypto library for Python? -- cgit v1.2.3 From 8f369b7cf68ae9018c04fea459d8b34587c3c6ed Mon Sep 17 00:00:00 2001 From: Konstantinos Koukopoulos Date: Tue, 28 Jan 2014 03:43:06 -0800 Subject: improve previous note on windows build --- docs/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 255c9e96..48ec0e45 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,7 +18,8 @@ You can install ``cryptography`` with ``pip``: If you're on Windows you'll need to make sure you have OpenSSL installed. There are `pre-compiled binaries`_ available. If your installation is in - an unusual location ``set LIB=C:\OpenSSL-x.y.z\lib;%LIB%`` before installing. + an unusual location set the ``LIB`` and ``INCLUDE`` environment variables + to include the corresponding locations. Why a new crypto library for Python? -- cgit v1.2.3 From 5484f72a8f83b8488bd22cae98be7c0c3576991a Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 05:46:15 -0800 Subject: Try to improve the docs --- docs/hazmat/primitives/interfaces.rst | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index ac48dd2c..ddccb773 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -211,27 +211,29 @@ Key Derivation Functions .. method:: derive(key_material) - :param key_material bytes: The raw key material. Depending on what key - derivation function you are using this could - be either random material, or a user + :param key_material bytes: The input key material. Depending on what + key derivation function you are using this + could be either random material, or a user supplied password. - :return: The resulting key. + :return: The new key. - The generates and returns a new key from the supplied key material. + This generates and returns a new key from the supplied key material. .. method:: verify(key_material, expected_key) - :param key_material bytes: The raw key material. This is the same as + :param key_material bytes: The input key material. This is the same as ``key_material`` in :meth:`derive`. - :param expected_key bytes: What the expected result of deriving a new - key is. + :param expected_key bytes: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. - This checks whether deriving a key from the supplied ``key_material`` - generates the same key as the ``expected_key``, and raises an exception - if they do not match. This can be used for something like checking - whether a user's password attempt matches the stored derived key. + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. This can be used for + something like checking whether a user's password attempt matches the + stored derived key. .. _`RSA`: http://en.wikipedia.org/wiki/RSA_(cryptosystem) -- cgit v1.2.3 From 5d9c6b207906c4ee084573b256b81a0c26da9b23 Mon Sep 17 00:00:00 2001 From: Konstantinos Koukopoulos Date: Tue, 28 Jan 2014 16:29:38 +0200 Subject: add example for windows installation --- docs/index.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 48ec0e45..b800bcaf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,7 +19,14 @@ You can install ``cryptography`` with ``pip``: If you're on Windows you'll need to make sure you have OpenSSL installed. There are `pre-compiled binaries`_ available. If your installation is in an unusual location set the ``LIB`` and ``INCLUDE`` environment variables - to include the corresponding locations. + to include the corresponding locations. For example: + + .. code-block:: console + + C:\> \path\to\vcvarsall.bat x86_amd64 + C:\> set LIB=C:\OpenSSL-1.0.1f-64bit\lib;%LIB% + C:\> set INCLUDE=C:\OpenSSL-1.0.1f-64bit\include;%INCLUDE% + C:\> pip install cryptography Why a new crypto library for Python? -- cgit v1.2.3 From e19e89f3e29c057a8250f5f63dde444b495259f7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 06:58:43 -0800 Subject: Enforce that these may only be called once --- docs/hazmat/primitives/interfaces.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index ddccb773..12644f4c 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -216,6 +216,11 @@ Key Derivation Functions could be either random material, or a user supplied password. :return: The new key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. This generates and returns a new key from the supplied key material. @@ -229,6 +234,11 @@ Key Derivation Functions :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and -- cgit v1.2.3 From 8454c5153537439b36b879e82ab3a3d8e7aa7909 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 07:01:54 -0800 Subject: Document that this is new in 0.2 --- docs/hazmat/primitives/interfaces.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 12644f4c..2adad913 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -209,6 +209,8 @@ Key Derivation Functions .. class:: KeyDerivationFunction + .. versionadded:: 0.2 + .. method:: derive(key_material) :param key_material bytes: The input key material. Depending on what -- cgit v1.2.3 From 1050ddf44f0713a587cd0ba239e23c95064a39bc Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 27 Jan 2014 21:04:03 -0600 Subject: PBKDF2 support for OpenSSL backend --- cryptography/hazmat/backends/interfaces.py | 15 ++++++++ cryptography/hazmat/backends/openssl/backend.py | 46 +++++++++++++++++++++++-- cryptography/hazmat/primitives/kdf/__init__.py | 0 cryptography/hazmat/primitives/kdf/pbkdf2.py | 46 +++++++++++++++++++++++++ docs/hazmat/backends/interfaces.rst | 39 +++++++++++++++++++++ pytest.ini | 3 +- tests/conftest.py | 3 +- tests/hazmat/primitives/test_pbkdf2_vectors.py | 37 ++++++++++++++++++++ tests/hazmat/primitives/utils.py | 25 ++++++++++++++ tests/utils.py | 4 +++ 10 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 cryptography/hazmat/primitives/kdf/__init__.py create mode 100644 cryptography/hazmat/primitives/kdf/pbkdf2.py create mode 100644 tests/hazmat/primitives/test_pbkdf2_vectors.py diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index 4fbb3488..936520eb 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -65,3 +65,18 @@ class HMACBackend(six.with_metaclass(abc.ABCMeta)): """ Create a HashContext for calculating a message authentication code. """ + + +class PBKDF2Backend(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def pbkdf2_hash_supported(self, algorithm): + """ + Return True if the hash algorithm is supported for PBKDF2 by this + backend. + """ + + @abc.abstractmethod + def derive_pbkdf2(self, algorithm, length, salt, iterations, key_material): + """ + Return length bytes derived from provided PBKDF2 parameters. + """ diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index d8d4669a..ca7d1778 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -20,9 +20,9 @@ from cryptography.exceptions import ( UnsupportedAlgorithm, InvalidTag, InternalError ) from cryptography.hazmat.backends.interfaces import ( - CipherBackend, HashBackend, HMACBackend + CipherBackend, HashBackend, HMACBackend, PBKDF2Backend ) -from cryptography.hazmat.primitives import interfaces +from cryptography.hazmat.primitives import interfaces, hashes from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, Blowfish, Camellia, TripleDES, ARC4, ) @@ -35,6 +35,7 @@ from cryptography.hazmat.bindings.openssl.binding import Binding @utils.register_interface(CipherBackend) @utils.register_interface(HashBackend) @utils.register_interface(HMACBackend) +@utils.register_interface(PBKDF2Backend) class Backend(object): """ OpenSSL API binding interfaces. @@ -133,6 +134,47 @@ class Backend(object): def create_symmetric_decryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + def pbkdf2_hash_supported(self, algorithm): + if self._lib.Cryptography_HAS_PBKDF2_HMAC: + digest = self._lib.EVP_get_digestbyname( + algorithm.name.encode("ascii")) + return digest != self._ffi.NULL + else: + return isinstance(algorithm, hashes.SHA1) + + def derive_pbkdf2(self, algorithm, length, salt, iterations, key_material): + buf = self._ffi.new("char[]", length) + if self._lib.Cryptography_HAS_PBKDF2_HMAC: + evp_md = self._lib.EVP_get_digestbyname( + algorithm.name.encode("ascii")) + assert evp_md != self._ffi.NULL + res = self._lib.PKCS5_PBKDF2_HMAC( + key_material, + len(key_material), + salt, + len(salt), + iterations, + evp_md, + length, + buf + ) + assert res == 1 + else: + # OpenSSL < 1.0.0 + assert isinstance(algorithm, hashes.SHA1) + res = self._lib.PKCS5_PBKDF2_HMAC_SHA1( + key_material, + len(key_material), + salt, + len(salt), + iterations, + length, + buf + ) + assert res == 1 + + return self._ffi.buffer(buf)[:] + def _handle_error(self, mode): code = self._lib.ERR_get_error() if not code and isinstance(mode, GCM): diff --git a/cryptography/hazmat/primitives/kdf/__init__.py b/cryptography/hazmat/primitives/kdf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py new file mode 100644 index 00000000..cc01246f --- /dev/null +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -0,0 +1,46 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +from cryptography.exceptions import InvalidKey, UnsupportedAlgorithm +from cryptography.hazmat.primitives import constant_time + + +class PBKDF2(object): + def __init__(self, algorithm, length, salt, iterations, backend): + if not backend.pbkdf2_hash_supported(algorithm): + raise UnsupportedAlgorithm( + "{0} is not supported by this backend".format(algorithm.name) + ) + self.algorithm = algorithm + if length > 2**31 - 1: + raise ValueError("Requested length too large.") + self._length = length + # TODO: handle salt + self._salt = salt + self.iterations = iterations + self._backend = backend + + def derive(self, key_material): + return self._backend.derive_pbkdf2( + self.algorithm, + self._length, + self._salt, + self.iterations, + key_material + ) + + def verify(self, key_material, expected_key): + if not constant_time.bytes_eq(key_material, expected_key): + raise InvalidKey("Signature did not match digest.") diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 11e2f2a2..fa4f800c 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -131,3 +131,42 @@ A specific ``backend`` may provide one or more of these interfaces. :returns: :class:`~cryptography.hazmat.primitives.interfaces.HashContext` + + + +.. class:: PBKDF2Backend + + A backend with methods for using PBKDF2. + + .. method:: pbkdf2_hash_supported(algorithm) + + Check if the specified ``algorithm`` is supported by this backend. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :returns: ``True`` if the specified ``algorithm`` is supported for + PBKDF2 by this backend, otherwise ``False``. + + .. method:: derive_pbkdf2(self, algorithm, length, salt, iterations, + key_material) + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param int length: The desired length of the derived key. Maximum is + 2\ :sup:`31` - 1. + + :param bytes salt: A salt. `RFC 2898`_ recommends 64-bits or longer. + + :param int iterations: The number of iterations to perform of the hash + function. + + :param bytes key_material: The key material to use as a basis for + the derived key. This is typically a password. + + :return bytes: Derived key. + +.. _`RFC 2898`: https://www.ietf.org/rfc/rfc2898.txt diff --git a/pytest.ini b/pytest.ini index 36d4edc4..89fda539 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,7 +1,8 @@ [pytest] addopts = -r s markers = - hmac: this test requires a backend providing HMACBackend cipher: this test requires a backend providing CipherBackend hash: this test requires a backend providing HashBackend + hmac: this test requires a backend providing HMACBackend + pbkdf2: this test requires a backend providing PBKDF2Backend supported: parametrized test requiring only_if and skip_message diff --git a/tests/conftest.py b/tests/conftest.py index a9acb54a..7370294f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ import pytest from cryptography.hazmat.backends import _ALL_BACKENDS from cryptography.hazmat.backends.interfaces import ( - HMACBackend, CipherBackend, HashBackend + HMACBackend, CipherBackend, HashBackend, PBKDF2Backend ) from .utils import check_for_iface, check_backend_support, select_backends @@ -21,6 +21,7 @@ def pytest_runtest_setup(item): check_for_iface("hmac", HMACBackend, item) check_for_iface("cipher", CipherBackend, item) check_for_iface("hash", HashBackend, item) + check_for_iface("pbkdf2", PBKDF2Backend, item) check_backend_support(item) diff --git a/tests/hazmat/primitives/test_pbkdf2_vectors.py b/tests/hazmat/primitives/test_pbkdf2_vectors.py new file mode 100644 index 00000000..e6e3935f --- /dev/null +++ b/tests/hazmat/primitives/test_pbkdf2_vectors.py @@ -0,0 +1,37 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.hazmat.primitives import hashes + +from .utils import generate_pbkdf2_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.pbkdf2_hash_supported(hashes.SHA1()), + skip_message="Does not support SHA1 for PBKDF2", +) +@pytest.mark.pbkdf2 +class TestPBKDF2_SHA1(object): + test_pbkdf2_sha1 = generate_pbkdf2_test( + load_nist_vectors, + "KDF", + [ + "rfc-6070-PBKDF2-SHA1.txt", + ], + hashes.SHA1(), + ) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index f27afe41..3a1d6d88 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -4,6 +4,7 @@ import os import pytest from cryptography.hazmat.primitives import hashes, hmac +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.exceptions import ( AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag, @@ -211,6 +212,30 @@ def hmac_test(backend, algorithm, params): assert h.finalize() == binascii.unhexlify(md.encode("ascii")) +def generate_pbkdf2_test(param_loader, path, file_names, algorithm): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_pbkdf2(self, backend, params): + pbkdf2_test(backend, algorithm, params) + return test_pbkdf2 + + +def pbkdf2_test(backend, algorithm, params): + # Password and salt can contain \0, which should be loaded as a null char. + # The NIST loader loads them as literal strings so we replace with the + # proper value. + kdf = PBKDF2( + algorithm, + int(params["length"]), + params["salt"], + int(params["iterations"]), + backend + ) + derived_key = kdf.derive(params["password"]) + assert binascii.hexlify(derived_key) == params["derived_key"] + + def generate_aead_exception_test(cipher_factory, mode_factory): def test_aead_exception(self, backend): aead_exception_test(backend, cipher_factory, mode_factory) diff --git a/tests/utils.py b/tests/utils.py index 507bc421..5c0e524f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -89,6 +89,10 @@ def load_nist_vectors(vector_data): # Build our data using a simple Key = Value format name, value = [c.strip() for c in line.split("=")] + # Some tests (PBKDF2) contain \0, which should be interpreted as a + # null character rather than literal. + value = value.replace("\\0", "\0") + # COUNT is a special token that indicates a new block of data if name.upper() == "COUNT": test_data = {} -- cgit v1.2.3 From b6d764c3f28837ed8854dfa836029a0b4650246f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 27 Jan 2014 22:32:11 -0600 Subject: pbkdf2 docs --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 2 +- docs/hazmat/backends/interfaces.rst | 3 +- docs/hazmat/primitives/index.rst | 1 + .../hazmat/primitives/key-derivation-functions.rst | 40 ++++++++++++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 docs/hazmat/primitives/key-derivation-functions.rst diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index cc01246f..014529b0 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -43,4 +43,4 @@ class PBKDF2(object): def verify(self, key_material, expected_key): if not constant_time.bytes_eq(key_material, expected_key): - raise InvalidKey("Signature did not match digest.") + raise InvalidKey("Keys do not match.") diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index fa4f800c..14ca6880 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -159,7 +159,7 @@ A specific ``backend`` may provide one or more of these interfaces. :param int length: The desired length of the derived key. Maximum is 2\ :sup:`31` - 1. - :param bytes salt: A salt. `RFC 2898`_ recommends 64-bits or longer. + :param bytes salt: A salt. :param int iterations: The number of iterations to perform of the hash function. @@ -169,4 +169,3 @@ A specific ``backend`` may provide one or more of these interfaces. :return bytes: Derived key. -.. _`RFC 2898`: https://www.ietf.org/rfc/rfc2898.txt diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst index b115fdbc..2a29bd8f 100644 --- a/docs/hazmat/primitives/index.rst +++ b/docs/hazmat/primitives/index.rst @@ -9,6 +9,7 @@ Primitives cryptographic-hashes hmac symmetric-encryption + key-derivation-functions padding constant-time interfaces diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst new file mode 100644 index 00000000..af2d910f --- /dev/null +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -0,0 +1,40 @@ +.. hazmat:: + +Key Derivation Functions +======================== + +.. currentmodule:: cryptography.hazmat.primitives.kdf + +Key derivation functions derive key material from information such as passwords +using a pseudo-random function (PRF). + +.. class:: PBKDF2(algorithm, length, salt, iterations, backend): + + .. doctest:: + + >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> salt = os.urandom(16) + >>> # derive + >>> kdf = PBKDF2(hashes.SHA1(), 20, salt, 10000, backend) + >>> key = kdf.derive(b"my great password") + >>> # verify + >>> kdf = PBKDF2(hashes.SHA1(), 20, salt, 10000, backend) + >>> kdf.verify(b"my great password", key) + None + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param int length: The desired length of the derived key. Maximum is + 2\ :sup:`31` - 1. + + :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or + longer. + + :param int iterations: The number of iterations to perform of the hash + function. + +.. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf -- cgit v1.2.3 From 6f2a04b4cf3cb938cdd58205a4fc7e8ddb6af299 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 12:05:51 -0600 Subject: test coverage, other changes --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 17 ++++++-- tests/hazmat/primitives/test_pbkdf2.py | 63 ++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 tests/hazmat/primitives/test_pbkdf2.py diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index 014529b0..27f9c7e2 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -13,26 +13,34 @@ from __future__ import absolute_import, division, print_function -from cryptography.exceptions import InvalidKey, UnsupportedAlgorithm -from cryptography.hazmat.primitives import constant_time +from cryptography import utils +from cryptography.exceptions import ( + InvalidKey, UnsupportedAlgorithm, AlreadyFinalized +) +from cryptography.hazmat.primitives import constant_time, interfaces +@utils.register_interface(interfaces.KeyDerivationFunction) class PBKDF2(object): def __init__(self, algorithm, length, salt, iterations, backend): if not backend.pbkdf2_hash_supported(algorithm): raise UnsupportedAlgorithm( "{0} is not supported by this backend".format(algorithm.name) ) + self._called = False self.algorithm = algorithm if length > 2**31 - 1: raise ValueError("Requested length too large.") self._length = length - # TODO: handle salt self._salt = salt self.iterations = iterations self._backend = backend def derive(self, key_material): + if self._called: + raise AlreadyFinalized("PBKDF2 instances can only be called once") + else: + self._called = True return self._backend.derive_pbkdf2( self.algorithm, self._length, @@ -42,5 +50,6 @@ class PBKDF2(object): ) def verify(self, key_material, expected_key): - if not constant_time.bytes_eq(key_material, expected_key): + derived_key = self.derive(key_material) + if not constant_time.bytes_eq(derived_key, expected_key): raise InvalidKey("Keys do not match.") diff --git a/tests/hazmat/primitives/test_pbkdf2.py b/tests/hazmat/primitives/test_pbkdf2.py new file mode 100644 index 00000000..6dd10129 --- /dev/null +++ b/tests/hazmat/primitives/test_pbkdf2.py @@ -0,0 +1,63 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography import utils +from cryptography.exceptions import ( + InvalidKey, UnsupportedAlgorithm, AlreadyFinalized +) +from cryptography.hazmat.primitives import hashes, interfaces +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 +from cryptography.hazmat.backends import default_backend + + +@utils.register_interface(interfaces.HashAlgorithm) +class UnsupportedDummyHash(object): + name = "unsupported-dummy-hash" + + +class TestPBKDF2(object): + def test_already_finalized(self): + kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf.derive(b"password") + with pytest.raises(AlreadyFinalized): + kdf.derive(b"password2") + + kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + key = kdf.derive(b"password") + with pytest.raises(AlreadyFinalized): + kdf.verify(b"password", key) + + kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf.verify(b"password", key) + with pytest.raises(AlreadyFinalized): + kdf.verify(b"password", key) + + def test_unsupported_algorithm(self): + with pytest.raises(UnsupportedAlgorithm): + PBKDF2(UnsupportedDummyHash(), 20, b"salt", 10, default_backend()) + + def test_invalid_key(self): + kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + key = kdf.derive(b"password") + + kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + with pytest.raises(InvalidKey): + kdf.verify(b"password2", key) + + def test_salt_too_long(self): + with pytest.raises(ValueError): + PBKDF2(hashes.SHA1(), 2**31, b"salt", 10, default_backend()) -- cgit v1.2.3 From 5d1af21519c728f1514efc1018eb427e7fb18559 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 12:19:32 -0600 Subject: documentation improvements for KDF --- docs/hazmat/backends/interfaces.rst | 4 ++- docs/hazmat/primitives/index.rst | 2 +- .../hazmat/primitives/key-derivation-functions.rst | 31 +++++++++++++--------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 14ca6880..975a7b02 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -136,6 +136,8 @@ A specific ``backend`` may provide one or more of these interfaces. .. class:: PBKDF2Backend + .. versionadded:: 0.2 + A backend with methods for using PBKDF2. .. method:: pbkdf2_hash_supported(algorithm) @@ -157,7 +159,7 @@ A specific ``backend`` may provide one or more of these interfaces. provider. :param int length: The desired length of the derived key. Maximum is - 2\ :sup:`31` - 1. + 2\ :sup:`31` - 1. :param bytes salt: A salt. diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst index 2a29bd8f..bde07392 100644 --- a/docs/hazmat/primitives/index.rst +++ b/docs/hazmat/primitives/index.rst @@ -9,7 +9,7 @@ Primitives cryptographic-hashes hmac symmetric-encryption - key-derivation-functions padding + key-derivation-functions constant-time interfaces diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index af2d910f..51d73bc2 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -10,8 +10,16 @@ using a pseudo-random function (PRF). .. class:: PBKDF2(algorithm, length, salt, iterations, backend): + .. versionadded:: 0.2 + + This class conforms to the + :class:`~cryptography.hazmat.primitives.interfaces.KeyDerivationFunction` + interface. + .. doctest:: + >>> import os + >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 >>> from cryptography.hazmat.backends import default_backend >>> backend = default_backend() @@ -22,19 +30,18 @@ using a pseudo-random function (PRF). >>> # verify >>> kdf = PBKDF2(hashes.SHA1(), 20, salt, 10000, backend) >>> kdf.verify(b"my great password", key) - None - - :param algorithm: An instance of a - :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` - provider. - :param int length: The desired length of the derived key. Maximum is + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + :param int length: The desired length of the derived key. Maximum is 2\ :sup:`31` - 1. - - :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or - longer. - - :param int iterations: The number of iterations to perform of the hash - function. + :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or + longer. + :param int iterations: The number of iterations to perform of the hash + function. + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + provider. .. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf -- cgit v1.2.3 From 611c27d1ec36f29276072d484882376e87e92728 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 10:40:46 -0800 Subject: Fixed #521 -- work on systems with no ec header at all --- cryptography/hazmat/bindings/openssl/ec.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cryptography/hazmat/bindings/openssl/ec.py b/cryptography/hazmat/bindings/openssl/ec.py index 9f10365a..57da7634 100644 --- a/cryptography/hazmat/bindings/openssl/ec.py +++ b/cryptography/hazmat/bindings/openssl/ec.py @@ -12,7 +12,10 @@ # limitations under the License. INCLUDES = """ +#ifdef OPENSSL_NO_EC #include +#endif + #include """ -- cgit v1.2.3 From 0c782cf19a1c79e1b95c01876ac020784a058506 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 11:03:45 -0800 Subject: Whoops, backwards --- cryptography/hazmat/bindings/openssl/ec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/bindings/openssl/ec.py b/cryptography/hazmat/bindings/openssl/ec.py index 57da7634..10bb011f 100644 --- a/cryptography/hazmat/bindings/openssl/ec.py +++ b/cryptography/hazmat/bindings/openssl/ec.py @@ -12,7 +12,7 @@ # limitations under the License. INCLUDES = """ -#ifdef OPENSSL_NO_EC +#ifndef OPENSSL_NO_EC #include #endif -- cgit v1.2.3 From 695313ff7cb1a7026a2624b8c61d495978d6f41c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 13:13:48 -0600 Subject: remove length check (which cffi handles) --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 2 -- tests/hazmat/primitives/test_pbkdf2.py | 4 ---- 2 files changed, 6 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index 27f9c7e2..1cc35f60 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -29,8 +29,6 @@ class PBKDF2(object): ) self._called = False self.algorithm = algorithm - if length > 2**31 - 1: - raise ValueError("Requested length too large.") self._length = length self._salt = salt self.iterations = iterations diff --git a/tests/hazmat/primitives/test_pbkdf2.py b/tests/hazmat/primitives/test_pbkdf2.py index 6dd10129..4d15d7a7 100644 --- a/tests/hazmat/primitives/test_pbkdf2.py +++ b/tests/hazmat/primitives/test_pbkdf2.py @@ -57,7 +57,3 @@ class TestPBKDF2(object): kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) with pytest.raises(InvalidKey): kdf.verify(b"password2", key) - - def test_salt_too_long(self): - with pytest.raises(ValueError): - PBKDF2(hashes.SHA1(), 2**31, b"salt", 10, default_backend()) -- cgit v1.2.3 From 98e40e658ef00dc6972f5420896bd57b385c8435 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 15:07:49 -0600 Subject: rename PBKDF2 to PBKDF2HMAC, address many other review comments --- cryptography/hazmat/backends/interfaces.py | 7 ++++--- cryptography/hazmat/backends/openssl/backend.py | 12 ++++++++---- cryptography/hazmat/primitives/kdf/pbkdf2.py | 5 +++-- docs/hazmat/backends/interfaces.rst | 16 ++++++++-------- docs/hazmat/primitives/key-derivation-functions.rst | 2 +- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index 936520eb..53c75181 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -67,16 +67,17 @@ class HMACBackend(six.with_metaclass(abc.ABCMeta)): """ -class PBKDF2Backend(six.with_metaclass(abc.ABCMeta)): +class PBKDF2HMACBackend(six.with_metaclass(abc.ABCMeta)): @abc.abstractmethod - def pbkdf2_hash_supported(self, algorithm): + def pbkdf2_hmac_supported(self, algorithm): """ Return True if the hash algorithm is supported for PBKDF2 by this backend. """ @abc.abstractmethod - def derive_pbkdf2(self, algorithm, length, salt, iterations, key_material): + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): """ Return length bytes derived from provided PBKDF2 parameters. """ diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index ca7d1778..dbdb2e56 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -20,7 +20,7 @@ from cryptography.exceptions import ( UnsupportedAlgorithm, InvalidTag, InternalError ) from cryptography.hazmat.backends.interfaces import ( - CipherBackend, HashBackend, HMACBackend, PBKDF2Backend + CipherBackend, HashBackend, HMACBackend, PBKDF2HMACBackend ) from cryptography.hazmat.primitives import interfaces, hashes from cryptography.hazmat.primitives.ciphers.algorithms import ( @@ -35,7 +35,7 @@ from cryptography.hazmat.bindings.openssl.binding import Binding @utils.register_interface(CipherBackend) @utils.register_interface(HashBackend) @utils.register_interface(HMACBackend) -@utils.register_interface(PBKDF2Backend) +@utils.register_interface(PBKDF2HMACBackend) class Backend(object): """ OpenSSL API binding interfaces. @@ -134,15 +134,19 @@ class Backend(object): def create_symmetric_decryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) - def pbkdf2_hash_supported(self, algorithm): + def pbkdf2_hmac_supported(self, algorithm): if self._lib.Cryptography_HAS_PBKDF2_HMAC: digest = self._lib.EVP_get_digestbyname( algorithm.name.encode("ascii")) return digest != self._ffi.NULL else: + # OpenSSL < 1.0.0 has an explicit PBKDF2-HMAC-SHA1 function, + # so if the PBKDF2_HMAC function is missing we only support + # SHA1 via PBKDF2_HMAC_SHA1. return isinstance(algorithm, hashes.SHA1) - def derive_pbkdf2(self, algorithm, length, salt, iterations, key_material): + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): buf = self._ffi.new("char[]", length) if self._lib.Cryptography_HAS_PBKDF2_HMAC: evp_md = self._lib.EVP_get_digestbyname( diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index 1cc35f60..940d9910 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -21,11 +21,12 @@ from cryptography.hazmat.primitives import constant_time, interfaces @utils.register_interface(interfaces.KeyDerivationFunction) -class PBKDF2(object): +class PBKDF2HMAC(object): def __init__(self, algorithm, length, salt, iterations, backend): if not backend.pbkdf2_hash_supported(algorithm): raise UnsupportedAlgorithm( - "{0} is not supported by this backend".format(algorithm.name) + "{0} is not supported for PBKDF2 by this backend".format( + algorithm.name) ) self._called = False self.algorithm = algorithm diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 975a7b02..e22c6bb3 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -134,32 +134,32 @@ A specific ``backend`` may provide one or more of these interfaces. -.. class:: PBKDF2Backend +.. class:: PBKDF2HMACBackend .. versionadded:: 0.2 - A backend with methods for using PBKDF2. + A backend with methods for using PBKDF2 using HMAC as a PRF. - .. method:: pbkdf2_hash_supported(algorithm) + .. method:: pbkdf2_hmac_supported(algorithm) Check if the specified ``algorithm`` is supported by this backend. - :param algorithm: An instance of a + :param prf: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. :returns: ``True`` if the specified ``algorithm`` is supported for - PBKDF2 by this backend, otherwise ``False``. + PBKDF2 HMAC by this backend, otherwise ``False``. - .. method:: derive_pbkdf2(self, algorithm, length, salt, iterations, - key_material) + .. method:: derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material) :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. :param int length: The desired length of the derived key. Maximum is - 2\ :sup:`31` - 1. + (2\ :sup:`32` - 1) * ``algorithm.digest_size`` :param bytes salt: A salt. diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 51d73bc2..bad7a36c 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -35,7 +35,7 @@ using a pseudo-random function (PRF). :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. :param int length: The desired length of the derived key. Maximum is - 2\ :sup:`31` - 1. + (2\ :sup:`32` - 1) * ``algorithm.digest_size`` :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or longer. :param int iterations: The number of iterations to perform of the hash -- cgit v1.2.3 From af5536972e26415ad5f4d685e7774ceac28a06be Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 13:37:42 -0800 Subject: This type is not defined either of course --- cryptography/hazmat/bindings/openssl/ec.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cryptography/hazmat/bindings/openssl/ec.py b/cryptography/hazmat/bindings/openssl/ec.py index 10bb011f..90481b9b 100644 --- a/cryptography/hazmat/bindings/openssl/ec.py +++ b/cryptography/hazmat/bindings/openssl/ec.py @@ -46,6 +46,7 @@ CUSTOMIZATIONS = """ static const long Cryptography_HAS_EC = 0; EC_KEY* (*EC_KEY_new_by_curve_name)(int) = NULL; void (*EC_KEY_free)(EC_KEY *) = NULL; +typedef void EC_KEY; #else static const long Cryptography_HAS_EC = 1; #endif -- cgit v1.2.3 From e8f7766acdbf24646b475d4d5ffd213c30f355a9 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 14:01:49 -0800 Subject: Doh, reorder --- cryptography/hazmat/bindings/openssl/ec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/bindings/openssl/ec.py b/cryptography/hazmat/bindings/openssl/ec.py index 90481b9b..06fd710c 100644 --- a/cryptography/hazmat/bindings/openssl/ec.py +++ b/cryptography/hazmat/bindings/openssl/ec.py @@ -44,9 +44,9 @@ MACROS = """ CUSTOMIZATIONS = """ #ifdef OPENSSL_NO_EC static const long Cryptography_HAS_EC = 0; +typedef void EC_KEY; EC_KEY* (*EC_KEY_new_by_curve_name)(int) = NULL; void (*EC_KEY_free)(EC_KEY *) = NULL; -typedef void EC_KEY; #else static const long Cryptography_HAS_EC = 1; #endif -- cgit v1.2.3 From 83b4eee23b78ac4b096099bad037bb89ce957a51 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 14:11:01 -0800 Subject: This needs to be first --- cryptography/hazmat/bindings/openssl/ec.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/bindings/openssl/ec.py b/cryptography/hazmat/bindings/openssl/ec.py index 06fd710c..042026fa 100644 --- a/cryptography/hazmat/bindings/openssl/ec.py +++ b/cryptography/hazmat/bindings/openssl/ec.py @@ -14,6 +14,8 @@ INCLUDES = """ #ifndef OPENSSL_NO_EC #include +#else +typedef void EC_KEY; #endif #include @@ -44,7 +46,6 @@ MACROS = """ CUSTOMIZATIONS = """ #ifdef OPENSSL_NO_EC static const long Cryptography_HAS_EC = 0; -typedef void EC_KEY; EC_KEY* (*EC_KEY_new_by_curve_name)(int) = NULL; void (*EC_KEY_free)(EC_KEY *) = NULL; #else -- cgit v1.2.3 From 628a1c45412f70180b3786b001b159510d9bb545 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 14:34:50 -0800 Subject: C is not a good programming language --- cryptography/hazmat/bindings/openssl/ec.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/bindings/openssl/ec.py b/cryptography/hazmat/bindings/openssl/ec.py index 042026fa..39403ff2 100644 --- a/cryptography/hazmat/bindings/openssl/ec.py +++ b/cryptography/hazmat/bindings/openssl/ec.py @@ -14,8 +14,6 @@ INCLUDES = """ #ifndef OPENSSL_NO_EC #include -#else -typedef void EC_KEY; #endif #include @@ -36,16 +34,17 @@ static const int NID_X9_62_prime256v1; """ FUNCTIONS = """ -EC_KEY *EC_KEY_new_by_curve_name(int); -void EC_KEY_free(EC_KEY *); """ MACROS = """ +EC_KEY *EC_KEY_new_by_curve_name(int); +void EC_KEY_free(EC_KEY *); """ CUSTOMIZATIONS = """ #ifdef OPENSSL_NO_EC static const long Cryptography_HAS_EC = 0; +typedef void EC_KEY; EC_KEY* (*EC_KEY_new_by_curve_name)(int) = NULL; void (*EC_KEY_free)(EC_KEY *) = NULL; #else -- cgit v1.2.3 From b3f763f1beae2a5fa1fdd3c27b6e9cb777ce7f50 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 16:42:15 -0600 Subject: finish PBKDF2HMAC rename, more docs --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 4 ++-- .../hazmat/primitives/key-derivation-functions.rst | 26 +++++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index 940d9910..a496cc27 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -23,7 +23,7 @@ from cryptography.hazmat.primitives import constant_time, interfaces @utils.register_interface(interfaces.KeyDerivationFunction) class PBKDF2HMAC(object): def __init__(self, algorithm, length, salt, iterations, backend): - if not backend.pbkdf2_hash_supported(algorithm): + if not backend.pbkdf2_hmac_supported(algorithm): raise UnsupportedAlgorithm( "{0} is not supported for PBKDF2 by this backend".format( algorithm.name) @@ -40,7 +40,7 @@ class PBKDF2HMAC(object): raise AlreadyFinalized("PBKDF2 instances can only be called once") else: self._called = True - return self._backend.derive_pbkdf2( + return self._backend.derive_pbkdf2_hmac( self.algorithm, self._length, self._salt, diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index bad7a36c..661b4611 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -8,7 +8,7 @@ Key Derivation Functions Key derivation functions derive key material from information such as passwords using a pseudo-random function (PRF). -.. class:: PBKDF2(algorithm, length, salt, iterations, backend): +.. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend): .. versionadded:: 0.2 @@ -20,28 +20,42 @@ using a pseudo-random function (PRF). >>> import os >>> from cryptography.hazmat.primitives import hashes - >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 + >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC >>> from cryptography.hazmat.backends import default_backend >>> backend = default_backend() >>> salt = os.urandom(16) >>> # derive - >>> kdf = PBKDF2(hashes.SHA1(), 20, salt, 10000, backend) + >>> kdf = PBKDF2HMAC( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... iterations=50000, + ... backend=backend + ... ) >>> key = kdf.derive(b"my great password") >>> # verify - >>> kdf = PBKDF2(hashes.SHA1(), 20, salt, 10000, backend) + >>> kdf = PBKDF2HMAC( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... iterations=50000, + ... backend=backend + ... ) >>> kdf.verify(b"my great password", key) :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. :param int length: The desired length of the derived key. Maximum is - (2\ :sup:`32` - 1) * ``algorithm.digest_size`` + (2\ :sup:`32` - 1) * ``algorithm.digest_size``. :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or longer. :param int iterations: The number of iterations to perform of the hash - function. + function. See OWASP's `Password Storage Cheat Sheet`_ for more + detailed recommendations. :param backend: A :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` provider. .. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf +.. _`Password Storage Cheat Sheet`: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet -- cgit v1.2.3 From 67d5c24f263875920aa87bf4dbd5eb250c03328c Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 14:42:41 -0800 Subject: Fuck, fix --- cryptography/hazmat/bindings/openssl/ssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/bindings/openssl/ssl.py b/cryptography/hazmat/bindings/openssl/ssl.py index cd872d18..2b4e54f1 100644 --- a/cryptography/hazmat/bindings/openssl/ssl.py +++ b/cryptography/hazmat/bindings/openssl/ssl.py @@ -393,6 +393,6 @@ CONDITIONAL_NAMES = { ], "Cryptography_HAS_EC": [ - "EC_KEY_new_by_curve_name", + "SSL_CTX_set_tmp_ecdh", ] } -- cgit v1.2.3 From 1277bc7e993dec8bbe64f1b5aeaaae6cff6429dd Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 17:09:59 -0600 Subject: okay this time really finish the rename. Up example iterations to 100k --- docs/hazmat/primitives/key-derivation-functions.rst | 4 ++-- pytest.ini | 2 +- tests/conftest.py | 4 ++-- tests/hazmat/primitives/test_pbkdf2.py | 20 ++++++++++---------- tests/hazmat/primitives/test_pbkdf2_vectors.py | 8 ++++---- tests/hazmat/primitives/utils.py | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 661b4611..4cb67701 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -29,7 +29,7 @@ using a pseudo-random function (PRF). ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=50000, + ... iterations=100000, ... backend=backend ... ) >>> key = kdf.derive(b"my great password") @@ -38,7 +38,7 @@ using a pseudo-random function (PRF). ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=50000, + ... iterations=100000, ... backend=backend ... ) >>> kdf.verify(b"my great password", key) diff --git a/pytest.ini b/pytest.ini index 89fda539..2a1b6e9f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,5 +4,5 @@ markers = cipher: this test requires a backend providing CipherBackend hash: this test requires a backend providing HashBackend hmac: this test requires a backend providing HMACBackend - pbkdf2: this test requires a backend providing PBKDF2Backend + pbkdf2hmac: this test requires a backend providing PBKDF2HMACBackend supported: parametrized test requiring only_if and skip_message diff --git a/tests/conftest.py b/tests/conftest.py index 7370294f..ecad1b23 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ import pytest from cryptography.hazmat.backends import _ALL_BACKENDS from cryptography.hazmat.backends.interfaces import ( - HMACBackend, CipherBackend, HashBackend, PBKDF2Backend + HMACBackend, CipherBackend, HashBackend, PBKDF2HMACBackend ) from .utils import check_for_iface, check_backend_support, select_backends @@ -21,7 +21,7 @@ def pytest_runtest_setup(item): check_for_iface("hmac", HMACBackend, item) check_for_iface("cipher", CipherBackend, item) check_for_iface("hash", HashBackend, item) - check_for_iface("pbkdf2", PBKDF2Backend, item) + check_for_iface("pbkdf2hmac", PBKDF2HMACBackend, item) check_backend_support(item) diff --git a/tests/hazmat/primitives/test_pbkdf2.py b/tests/hazmat/primitives/test_pbkdf2.py index 4d15d7a7..41123557 100644 --- a/tests/hazmat/primitives/test_pbkdf2.py +++ b/tests/hazmat/primitives/test_pbkdf2.py @@ -20,40 +20,40 @@ from cryptography.exceptions import ( InvalidKey, UnsupportedAlgorithm, AlreadyFinalized ) from cryptography.hazmat.primitives import hashes, interfaces -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.backends import default_backend @utils.register_interface(interfaces.HashAlgorithm) -class UnsupportedDummyHash(object): - name = "unsupported-dummy-hash" +class DummyHash(object): + name = "dummy-hash" -class TestPBKDF2(object): +class TestPBKDF2HMAC(object): def test_already_finalized(self): - kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) kdf.derive(b"password") with pytest.raises(AlreadyFinalized): kdf.derive(b"password2") - kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) key = kdf.derive(b"password") with pytest.raises(AlreadyFinalized): kdf.verify(b"password", key) - kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) kdf.verify(b"password", key) with pytest.raises(AlreadyFinalized): kdf.verify(b"password", key) def test_unsupported_algorithm(self): with pytest.raises(UnsupportedAlgorithm): - PBKDF2(UnsupportedDummyHash(), 20, b"salt", 10, default_backend()) + PBKDF2HMAC(DummyHash(), 20, b"salt", 10, default_backend()) def test_invalid_key(self): - kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) key = kdf.derive(b"password") - kdf = PBKDF2(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) with pytest.raises(InvalidKey): kdf.verify(b"password2", key) diff --git a/tests/hazmat/primitives/test_pbkdf2_vectors.py b/tests/hazmat/primitives/test_pbkdf2_vectors.py index e6e3935f..cbd4cc9d 100644 --- a/tests/hazmat/primitives/test_pbkdf2_vectors.py +++ b/tests/hazmat/primitives/test_pbkdf2_vectors.py @@ -22,11 +22,11 @@ from ...utils import load_nist_vectors @pytest.mark.supported( - only_if=lambda backend: backend.pbkdf2_hash_supported(hashes.SHA1()), - skip_message="Does not support SHA1 for PBKDF2", + only_if=lambda backend: backend.pbkdf2_hmac_supported(hashes.SHA1()), + skip_message="Does not support SHA1 for PBKDF2HMAC", ) -@pytest.mark.pbkdf2 -class TestPBKDF2_SHA1(object): +@pytest.mark.pbkdf2hmac +class TestPBKDF2HMAC_SHA1(object): test_pbkdf2_sha1 = generate_pbkdf2_test( load_nist_vectors, "KDF", diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 3a1d6d88..6b1d055d 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -4,7 +4,7 @@ import os import pytest from cryptography.hazmat.primitives import hashes, hmac -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2 +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.exceptions import ( AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag, @@ -225,7 +225,7 @@ def pbkdf2_test(backend, algorithm, params): # Password and salt can contain \0, which should be loaded as a null char. # The NIST loader loads them as literal strings so we replace with the # proper value. - kdf = PBKDF2( + kdf = PBKDF2HMAC( algorithm, int(params["length"]), params["salt"], -- cgit v1.2.3 From 3d8c66f9a01b8982902f69ae960fcc85aa43bfb8 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 17:36:50 -0600 Subject: update docs with more detailed info re: PBKDF2 usage --- .../hazmat/primitives/key-derivation-functions.rst | 51 ++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 4cb67701..e16a9900 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -5,13 +5,21 @@ Key Derivation Functions .. currentmodule:: cryptography.hazmat.primitives.kdf -Key derivation functions derive key material from information such as passwords -using a pseudo-random function (PRF). +Key derivation functions derive key material from passwords or other data +sources using a pseudo-random function (PRF). Each KDF is suitable for +different tasks (cryptographic key derivation, password storage, +key stretching) so match your needs to their capabilities. .. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend): .. versionadded:: 0.2 + PBKDF2 (Password Based Key Derivation Function 2) is typically used for + deriving a cryptographic key from a password. It may also be used for + key storage, but other key storage KDFs such as `scrypt`_ or `bcrypt`_ + are generally considered better solutions since they are designed to be + slow. + This class conforms to the :class:`~cryptography.hazmat.primitives.interfaces.KeyDerivationFunction` interface. @@ -52,10 +60,47 @@ using a pseudo-random function (PRF). longer. :param int iterations: The number of iterations to perform of the hash function. See OWASP's `Password Storage Cheat Sheet`_ for more - detailed recommendations. + detailed recommendations if you intend to use this for password storage. :param backend: A :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` provider. + .. method:: derive(key_material) + + :param key_material bytes: The input key material. For PBKDF2 this + should be a password. + :return: The new key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This generates and returns a new key from the supplied password. + + .. method:: verify(key_material, expected_key) + + :param key_material bytes: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param expected_key bytes: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. This can be used for + checking whether a user's password attempt matches the stored derived + key. + .. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf .. _`Password Storage Cheat Sheet`: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet +.. _`bcrypt`: http://en.wikipedia.org/wiki/Bcrypt +.. _`scrypt`: http://en.wikipedia.org/wiki/Scrypt -- cgit v1.2.3 From 560ef47949077bbe89f108e1f3349cba4eacbbf0 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 17:44:10 -0600 Subject: add PBKDF2HMAC to changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 545d6945..cc0aacd3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,7 @@ Changelog :class:`~cryptography.hazmat.backends.interfaces.CipherBackend`. * Added support for the OpenSSL backend under Windows. * Improved thread-safety for the OpenSSL backend. +* Added PBKDF2HMAC support to OpenSSL backend. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 8a39c51dd5a8eb501411a7f90bb4001f96a99e20 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 28 Jan 2014 16:22:50 -0800 Subject: Add this to the changelog --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 545d6945..5a8e9809 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,8 @@ Changelog :class:`~cryptography.hazmat.backends.interfaces.CipherBackend`. * Added support for the OpenSSL backend under Windows. * Improved thread-safety for the OpenSSL backend. +* Fixed compilation on systems where OpenSSL's ``ec.h`` header is not + available, such as CentOS. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From aad090fe4273ee80b28f4dc6d1b56cce483c85f7 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 18:28:43 -0600 Subject: simplify pbkdf2_hmac_supported in openssl backend --- cryptography/hazmat/backends/openssl/backend.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index dbdb2e56..cf931dab 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -136,9 +136,7 @@ class Backend(object): def pbkdf2_hmac_supported(self, algorithm): if self._lib.Cryptography_HAS_PBKDF2_HMAC: - digest = self._lib.EVP_get_digestbyname( - algorithm.name.encode("ascii")) - return digest != self._ffi.NULL + return self.hmac_supported(algorithm) else: # OpenSSL < 1.0.0 has an explicit PBKDF2-HMAC-SHA1 function, # so if the PBKDF2_HMAC function is missing we only support -- cgit v1.2.3 From 5c8ea70ca7a36a0e090640b329bd9931232b7b23 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 19:23:01 -0600 Subject: add some unicode checks for salt on init and key_material on derive --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 13 +++++++++++++ tests/hazmat/primitives/test_pbkdf2.py | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index a496cc27..fec1d5c2 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -13,6 +13,8 @@ from __future__ import absolute_import, division, print_function +import six + from cryptography import utils from cryptography.exceptions import ( InvalidKey, UnsupportedAlgorithm, AlreadyFinalized @@ -31,6 +33,11 @@ class PBKDF2HMAC(object): self._called = False self.algorithm = algorithm self._length = length + if isinstance(salt, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as key " + "material." + ) self._salt = salt self.iterations = iterations self._backend = backend @@ -40,6 +47,12 @@ class PBKDF2HMAC(object): raise AlreadyFinalized("PBKDF2 instances can only be called once") else: self._called = True + + if isinstance(key_material, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as key " + "material." + ) return self._backend.derive_pbkdf2_hmac( self.algorithm, self._length, diff --git a/tests/hazmat/primitives/test_pbkdf2.py b/tests/hazmat/primitives/test_pbkdf2.py index 41123557..6ad225a8 100644 --- a/tests/hazmat/primitives/test_pbkdf2.py +++ b/tests/hazmat/primitives/test_pbkdf2.py @@ -14,6 +14,7 @@ from __future__ import absolute_import, division, print_function import pytest +import six from cryptography import utils from cryptography.exceptions import ( @@ -57,3 +58,12 @@ class TestPBKDF2HMAC(object): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) with pytest.raises(InvalidKey): kdf.verify(b"password2", key) + + def test_unicode_error_with_salt(self): + with pytest.raises(TypeError): + PBKDF2HMAC(hashes.SHA1(), 20, six.u("salt"), 10, default_backend()) + + def test_unicode_error_with_key_material(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + with pytest.raises(TypeError): + kdf.derive(six.u("unicode here")) -- cgit v1.2.3 From 2e8617bea70e1b29b4138f591c2264041f5b1099 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 19:27:06 -0600 Subject: rename test files --- tests/hazmat/primitives/test_pbkdf2.py | 69 ---------------------- tests/hazmat/primitives/test_pbkdf2_vectors.py | 37 ------------ tests/hazmat/primitives/test_pbkdf2hmac.py | 69 ++++++++++++++++++++++ tests/hazmat/primitives/test_pbkdf2hmac_vectors.py | 37 ++++++++++++ 4 files changed, 106 insertions(+), 106 deletions(-) delete mode 100644 tests/hazmat/primitives/test_pbkdf2.py delete mode 100644 tests/hazmat/primitives/test_pbkdf2_vectors.py create mode 100644 tests/hazmat/primitives/test_pbkdf2hmac.py create mode 100644 tests/hazmat/primitives/test_pbkdf2hmac_vectors.py diff --git a/tests/hazmat/primitives/test_pbkdf2.py b/tests/hazmat/primitives/test_pbkdf2.py deleted file mode 100644 index 6ad225a8..00000000 --- a/tests/hazmat/primitives/test_pbkdf2.py +++ /dev/null @@ -1,69 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import, division, print_function - -import pytest -import six - -from cryptography import utils -from cryptography.exceptions import ( - InvalidKey, UnsupportedAlgorithm, AlreadyFinalized -) -from cryptography.hazmat.primitives import hashes, interfaces -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -from cryptography.hazmat.backends import default_backend - - -@utils.register_interface(interfaces.HashAlgorithm) -class DummyHash(object): - name = "dummy-hash" - - -class TestPBKDF2HMAC(object): - def test_already_finalized(self): - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - kdf.derive(b"password") - with pytest.raises(AlreadyFinalized): - kdf.derive(b"password2") - - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - key = kdf.derive(b"password") - with pytest.raises(AlreadyFinalized): - kdf.verify(b"password", key) - - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - kdf.verify(b"password", key) - with pytest.raises(AlreadyFinalized): - kdf.verify(b"password", key) - - def test_unsupported_algorithm(self): - with pytest.raises(UnsupportedAlgorithm): - PBKDF2HMAC(DummyHash(), 20, b"salt", 10, default_backend()) - - def test_invalid_key(self): - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - key = kdf.derive(b"password") - - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - with pytest.raises(InvalidKey): - kdf.verify(b"password2", key) - - def test_unicode_error_with_salt(self): - with pytest.raises(TypeError): - PBKDF2HMAC(hashes.SHA1(), 20, six.u("salt"), 10, default_backend()) - - def test_unicode_error_with_key_material(self): - kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) - with pytest.raises(TypeError): - kdf.derive(six.u("unicode here")) diff --git a/tests/hazmat/primitives/test_pbkdf2_vectors.py b/tests/hazmat/primitives/test_pbkdf2_vectors.py deleted file mode 100644 index cbd4cc9d..00000000 --- a/tests/hazmat/primitives/test_pbkdf2_vectors.py +++ /dev/null @@ -1,37 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import, division, print_function - -import pytest - -from cryptography.hazmat.primitives import hashes - -from .utils import generate_pbkdf2_test -from ...utils import load_nist_vectors - - -@pytest.mark.supported( - only_if=lambda backend: backend.pbkdf2_hmac_supported(hashes.SHA1()), - skip_message="Does not support SHA1 for PBKDF2HMAC", -) -@pytest.mark.pbkdf2hmac -class TestPBKDF2HMAC_SHA1(object): - test_pbkdf2_sha1 = generate_pbkdf2_test( - load_nist_vectors, - "KDF", - [ - "rfc-6070-PBKDF2-SHA1.txt", - ], - hashes.SHA1(), - ) diff --git a/tests/hazmat/primitives/test_pbkdf2hmac.py b/tests/hazmat/primitives/test_pbkdf2hmac.py new file mode 100644 index 00000000..6ad225a8 --- /dev/null +++ b/tests/hazmat/primitives/test_pbkdf2hmac.py @@ -0,0 +1,69 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import pytest +import six + +from cryptography import utils +from cryptography.exceptions import ( + InvalidKey, UnsupportedAlgorithm, AlreadyFinalized +) +from cryptography.hazmat.primitives import hashes, interfaces +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from cryptography.hazmat.backends import default_backend + + +@utils.register_interface(interfaces.HashAlgorithm) +class DummyHash(object): + name = "dummy-hash" + + +class TestPBKDF2HMAC(object): + def test_already_finalized(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf.derive(b"password") + with pytest.raises(AlreadyFinalized): + kdf.derive(b"password2") + + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + key = kdf.derive(b"password") + with pytest.raises(AlreadyFinalized): + kdf.verify(b"password", key) + + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf.verify(b"password", key) + with pytest.raises(AlreadyFinalized): + kdf.verify(b"password", key) + + def test_unsupported_algorithm(self): + with pytest.raises(UnsupportedAlgorithm): + PBKDF2HMAC(DummyHash(), 20, b"salt", 10, default_backend()) + + def test_invalid_key(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + key = kdf.derive(b"password") + + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + with pytest.raises(InvalidKey): + kdf.verify(b"password2", key) + + def test_unicode_error_with_salt(self): + with pytest.raises(TypeError): + PBKDF2HMAC(hashes.SHA1(), 20, six.u("salt"), 10, default_backend()) + + def test_unicode_error_with_key_material(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + with pytest.raises(TypeError): + kdf.derive(six.u("unicode here")) diff --git a/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py new file mode 100644 index 00000000..cbd4cc9d --- /dev/null +++ b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py @@ -0,0 +1,37 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.hazmat.primitives import hashes + +from .utils import generate_pbkdf2_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.pbkdf2_hmac_supported(hashes.SHA1()), + skip_message="Does not support SHA1 for PBKDF2HMAC", +) +@pytest.mark.pbkdf2hmac +class TestPBKDF2HMAC_SHA1(object): + test_pbkdf2_sha1 = generate_pbkdf2_test( + load_nist_vectors, + "KDF", + [ + "rfc-6070-PBKDF2-SHA1.txt", + ], + hashes.SHA1(), + ) -- cgit v1.2.3 From 99d5190656184ad791e50eab72c631e7f829e283 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 20:16:20 -0600 Subject: some alternate language --- docs/hazmat/primitives/key-derivation-functions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index e16a9900..c77b763a 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -97,7 +97,7 @@ key stretching) so match your needs to their capabilities. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. This can be used for - checking whether a user's password attempt matches the stored derived + checking whether the password a user provides matches the stored derived key. .. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf -- cgit v1.2.3 From 5ce6901b2dc7e492e3e39c001821beca96c58906 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 27 Jan 2014 22:39:57 -0600 Subject: commoncrypto PBKDF2 support --- .../hazmat/backends/commoncrypto/backend.py | 37 +++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 4e70cab5..e3466457 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -20,7 +20,7 @@ from cryptography.exceptions import ( UnsupportedAlgorithm, InvalidTag, InternalError ) from cryptography.hazmat.backends.interfaces import ( - HashBackend, HMACBackend, CipherBackend + HashBackend, HMACBackend, CipherBackend, PBKDF2Backend ) from cryptography.hazmat.bindings.commoncrypto.binding import Binding from cryptography.hazmat.primitives import interfaces, constant_time @@ -40,6 +40,7 @@ HashMethods = namedtuple( @utils.register_interface(CipherBackend) @utils.register_interface(HashBackend) @utils.register_interface(HMACBackend) +@utils.register_interface(PBKDF2Backend) class Backend(object): """ CommonCrypto API wrapper. @@ -89,6 +90,14 @@ class Backend(object): "sha512": self._lib.kCCHmacAlgSHA512, } + self._supported_pbkdf2_algorithms = { + "sha1": self._lib.kCCPRFHmacAlgSHA1, + "sha224": self._lib.kCCPRFHmacAlgSHA224, + "sha256": self._lib.kCCPRFHmacAlgSHA256, + "sha384": self._lib.kCCPRFHmacAlgSHA384, + "sha512": self._lib.kCCPRFHmacAlgSHA512, + } + def hash_supported(self, algorithm): try: self._hash_mapping[algorithm.name] @@ -134,6 +143,32 @@ class Backend(object): else: return _CipherContext(self, cipher, mode, self._lib.kCCDecrypt) + def pbkdf2_hash_supported(self, algorithm): + try: + self._supported_pbkdf2_algorithms[algorithm.name] + except KeyError: + return False + else: + return True + + def derive_pbkdf2(self, algorithm, length, salt, iterations, key_material): + alg_enum = self._supported_pbkdf2_algorithms[algorithm.name] + buf = self._ffi.new("char[]", length) + res = self._lib.CCKeyDerivationPBKDF( + self._lib.kCCPBKDF2, + key_material, + len(key_material), + salt, + len(salt), + alg_enum, + iterations, + buf, + length + ) + self._check_response(res) + + return self._ffi.buffer(buf)[:] + def _register_cipher_adapter(self, cipher_cls, cipher_const, mode_cls, mode_const): if (cipher_cls, mode_cls) in self._cipher_registry: -- cgit v1.2.3 From 92e801a41d6c4938e9833b0397112ca855b4355b Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 21:18:52 -0600 Subject: update commoncrypto pbkdf2 with new naming --- cryptography/hazmat/backends/commoncrypto/backend.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index e3466457..ce40bb38 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -20,7 +20,7 @@ from cryptography.exceptions import ( UnsupportedAlgorithm, InvalidTag, InternalError ) from cryptography.hazmat.backends.interfaces import ( - HashBackend, HMACBackend, CipherBackend, PBKDF2Backend + HashBackend, HMACBackend, CipherBackend, PBKDF2HMACBackend ) from cryptography.hazmat.bindings.commoncrypto.binding import Binding from cryptography.hazmat.primitives import interfaces, constant_time @@ -40,7 +40,7 @@ HashMethods = namedtuple( @utils.register_interface(CipherBackend) @utils.register_interface(HashBackend) @utils.register_interface(HMACBackend) -@utils.register_interface(PBKDF2Backend) +@utils.register_interface(PBKDF2HMACBackend) class Backend(object): """ CommonCrypto API wrapper. @@ -90,7 +90,7 @@ class Backend(object): "sha512": self._lib.kCCHmacAlgSHA512, } - self._supported_pbkdf2_algorithms = { + self._supported_pbkdf2_hmac_algorithms = { "sha1": self._lib.kCCPRFHmacAlgSHA1, "sha224": self._lib.kCCPRFHmacAlgSHA224, "sha256": self._lib.kCCPRFHmacAlgSHA256, @@ -143,16 +143,17 @@ class Backend(object): else: return _CipherContext(self, cipher, mode, self._lib.kCCDecrypt) - def pbkdf2_hash_supported(self, algorithm): + def pbkdf2_hmac_supported(self, algorithm): try: - self._supported_pbkdf2_algorithms[algorithm.name] + self._supported_pbkdf2_hmac_algorithms[algorithm.name] except KeyError: return False else: return True - def derive_pbkdf2(self, algorithm, length, salt, iterations, key_material): - alg_enum = self._supported_pbkdf2_algorithms[algorithm.name] + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): + alg_enum = self._supported_pbkdf2_hmac_algorithms[algorithm.name] buf = self._ffi.new("char[]", length) res = self._lib.CCKeyDerivationPBKDF( self._lib.kCCPBKDF2, -- cgit v1.2.3 From 0bf1f138d5a504ddf07279c42632702265090f76 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 21:20:01 -0600 Subject: update changelog --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index be42b5db..14019c81 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,7 +14,7 @@ Changelog * Improved thread-safety for the OpenSSL backend. * Fixed compilation on systems where OpenSSL's ``ec.h`` header is not available, such as CentOS. -* Added PBKDF2HMAC support to OpenSSL backend. +* Added PBKDF2HMAC support to OpenSSL and CommonCrypto backends. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 589b90826db025bcd7fc02e29b4831a09df3269d Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 21:25:41 -0600 Subject: doc updates based on review --- docs/hazmat/backends/interfaces.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index e22c6bb3..ca3a5433 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -133,7 +133,6 @@ A specific ``backend`` may provide one or more of these interfaces. :class:`~cryptography.hazmat.primitives.interfaces.HashContext` - .. class:: PBKDF2HMACBackend .. versionadded:: 0.2 @@ -144,7 +143,7 @@ A specific ``backend`` may provide one or more of these interfaces. Check if the specified ``algorithm`` is supported by this backend. - :param prf: An instance of a + :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider. -- cgit v1.2.3 From d6b02d410370c439e662e8a2f5741a2d092bab8e Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 28 Jan 2014 21:25:53 -0600 Subject: review fixes --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index fec1d5c2..2c8dd541 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -30,7 +30,7 @@ class PBKDF2HMAC(object): "{0} is not supported for PBKDF2 by this backend".format( algorithm.name) ) - self._called = False + self._used = False self.algorithm = algorithm self._length = length if isinstance(salt, six.text_type): @@ -43,10 +43,9 @@ class PBKDF2HMAC(object): self._backend = backend def derive(self, key_material): - if self._called: + if self._used: raise AlreadyFinalized("PBKDF2 instances can only be called once") - else: - self._called = True + self._used = True if isinstance(key_material, six.text_type): raise TypeError( -- cgit v1.2.3 From 173982ed9df1604bd905f0076c66fc5e4cf31514 Mon Sep 17 00:00:00 2001 From: Konstantinos Koukopoulos Date: Wed, 29 Jan 2014 10:42:45 +0200 Subject: add Konstantinos Koukopoulos to AUTHORS.rst --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index ad27cec6..b9dc459f 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -12,3 +12,4 @@ PGP key fingerprints are enclosed in parentheses. * Jarret Raim * Alex Stapleton (A1C7 E50B 66DE 39ED C847 9665 8E3C 20D1 9BD9 5C4C) * David Reid (0F83 CC87 B32F 482B C726 B58A 9FBF D8F4 DA89 6D74) +* Konstantinos Koukopoulos (D6BD 52B6 8C99 A91C E2C8 934D 3300 566B 3A46 726E) -- cgit v1.2.3 From ab09605c72eaa73cb8672a822a3a3112a59f12b3 Mon Sep 17 00:00:00 2001 From: Stephen Holsapple Date: Tue, 28 Jan 2014 18:00:24 -0800 Subject: Add bindings for X509_REQ_get_extensions. Without this binding it is impossible to get any extension objects from an X509Req object. --- AUTHORS.rst | 1 + cryptography/hazmat/bindings/openssl/x509.py | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index b9dc459f..0e3979ad 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -13,3 +13,4 @@ PGP key fingerprints are enclosed in parentheses. * Alex Stapleton (A1C7 E50B 66DE 39ED C847 9665 8E3C 20D1 9BD9 5C4C) * David Reid (0F83 CC87 B32F 482B C726 B58A 9FBF D8F4 DA89 6D74) * Konstantinos Koukopoulos (D6BD 52B6 8C99 A91C E2C8 934D 3300 566B 3A46 726E) +* Stephen Holsapple diff --git a/cryptography/hazmat/bindings/openssl/x509.py b/cryptography/hazmat/bindings/openssl/x509.py index 840254a2..d492ea01 100644 --- a/cryptography/hazmat/bindings/openssl/x509.py +++ b/cryptography/hazmat/bindings/openssl/x509.py @@ -119,6 +119,7 @@ int X509_REQ_sign(X509_REQ *, EVP_PKEY *, const EVP_MD *); int X509_REQ_verify(X509_REQ *, EVP_PKEY *); EVP_PKEY *X509_REQ_get_pubkey(X509_REQ *); int X509_REQ_add_extensions(X509_REQ *, X509_EXTENSIONS *); +X509_EXTENSIONS *X509_REQ_get_extensions(X509_REQ *); int X509_REQ_print_ex(BIO *, X509_REQ *, unsigned long, unsigned long); int X509V3_EXT_print(BIO *, X509_EXTENSION *, unsigned long, int); -- cgit v1.2.3 From 298e533e01053a5fc1ba00ba640a3daf128d1151 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 11:16:22 -0600 Subject: update docs for pbkdf2 --- docs/hazmat/primitives/key-derivation-functions.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index c77b763a..e652ecbf 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -14,11 +14,10 @@ key stretching) so match your needs to their capabilities. .. versionadded:: 0.2 - PBKDF2 (Password Based Key Derivation Function 2) is typically used for + `PBKDF2`_ (Password Based Key Derivation Function 2) is typically used for deriving a cryptographic key from a password. It may also be used for - key storage, but other key storage KDFs such as `scrypt`_ or `bcrypt`_ - are generally considered better solutions since they are designed to be - slow. + key storage, but an alternate key storage KDF such as `scrypt` is generally + considered a better solution since it is designed to be slow. This class conforms to the :class:`~cryptography.hazmat.primitives.interfaces.KeyDerivationFunction` @@ -102,5 +101,5 @@ key stretching) so match your needs to their capabilities. .. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf .. _`Password Storage Cheat Sheet`: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet -.. _`bcrypt`: http://en.wikipedia.org/wiki/Bcrypt +.. _`PBKDF2`: http://en.wikipedia.org/wiki/PBKDF2 .. _`scrypt`: http://en.wikipedia.org/wiki/Scrypt -- cgit v1.2.3 From bc26efb3668a3f66fd13a6a8d3ef72f91e11af8e Mon Sep 17 00:00:00 2001 From: skeuomorf Date: Wed, 29 Jan 2014 08:31:47 +0200 Subject: Added canonical installation document with details about various platforms, fixes #519 --- docs/hazmat/backends/openssl.rst | 48 ------------------------- docs/index.rst | 31 ++++------------ docs/installation.rst | 76 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 72 deletions(-) create mode 100644 docs/installation.rst diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst index 926ec7d1..12d2d9f6 100644 --- a/docs/hazmat/backends/openssl.rst +++ b/docs/hazmat/backends/openssl.rst @@ -13,52 +13,4 @@ The `OpenSSL`_ C library. The string name of this backend: ``"openssl"`` -Using your own OpenSSL on Linux -------------------------------- - -Python links to OpenSSL for its own purposes and this can sometimes cause -problems when you wish to use a different version of OpenSSL with cryptography. -If you want to use cryptography with your own build of OpenSSL you will need to -make sure that the build is configured correctly so that your version of -OpenSSL doesn't conflict with Python's. - -The options you need to add allow the linker to identify every symbol correctly -even when multiple versions of the library are linked into the same program. If -you are using your distribution's source packages these will probably be -patched in for you already, otherwise you'll need to use options something like -this when configuring OpenSSL: - -.. code-block:: console - - $ ./config -Wl,--version-script=openssl.ld -Wl,-Bsymbolic-functions -fPIC shared - -You'll also need to generate your own ``openssl.ld`` file. For example:: - - OPENSSL_1.0.1F_CUSTOM { - global: - *; - }; - -You should replace the version string on the first line as appropriate for your -build. - -Using your own OpenSSL on OS X ------------------------------- - -To link cryptography against a custom version of OpenSSL you'll need to set -``ARCHFLAGS``, ``LDFLAGS``, and ``CFLAGS``. OpenSSL can be installed via -`Homebrew`_: - -.. code-block:: console - - $ brew install openssl - -Then install cryptography linking against the brewed version: - -.. code-block:: console - - $ env ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography - - .. _`OpenSSL`: https://www.openssl.org/ -.. _`Homebrew`: http://brew.sh diff --git a/docs/index.rst b/docs/index.rst index b800bcaf..73c40bf0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,30 +5,6 @@ Welcome to ``cryptography`` primitives. We hope it'll be your one-stop-shop for all your cryptographic needs in Python. -Installing ----------- - -You can install ``cryptography`` with ``pip``: - -.. code-block:: console - - $ pip install cryptography - -.. note:: - - If you're on Windows you'll need to make sure you have OpenSSL installed. - There are `pre-compiled binaries`_ available. If your installation is in - an unusual location set the ``LIB`` and ``INCLUDE`` environment variables - to include the corresponding locations. For example: - - .. code-block:: console - - C:\> \path\to\vcvarsall.bat x86_amd64 - C:\> set LIB=C:\OpenSSL-1.0.1f-64bit\lib;%LIB% - C:\> set INCLUDE=C:\OpenSSL-1.0.1f-64bit\include;%INCLUDE% - C:\> pip install cryptography - - Why a new crypto library for Python? ------------------------------------ @@ -64,6 +40,13 @@ admonition at the top. We recommend using the recipes layer whenever possible, and falling back to the hazmat layer only when necessary. +Installation +~~~~~~~~~~~~ +.. toctree:: + :maxdepth: 2 + + installation + The recipes layer ~~~~~~~~~~~~~~~~~ diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 00000000..d9c30801 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,76 @@ +Installing +========== + +You can install ``cryptography`` with ``pip``: + +.. code-block:: console + + $ pip install cryptography + +Installation Notes +================== +On Windows +---------- +.. note:: + + If you're on Windows you'll need to make sure you have OpenSSL installed. + There are `pre-compiled binaries`_ available. If your installation is in + an unusual location set the ``LIB`` and ``INCLUDE`` environment variables + to include the corresponding locations. For example: + + .. code-block:: console + + C:\> \path\to\vcvarsall.bat x86_amd64 + C:\> set LIB=C:\OpenSSL-1.0.1f-64bit\lib;%LIB% + C:\> set INCLUDE=C:\OpenSSL-1.0.1f-64bit\include;%INCLUDE% + C:\> pip install cryptography + +Using your own OpenSSL on Linux +------------------------------- + +Python links to OpenSSL for its own purposes and this can sometimes cause +problems when you wish to use a different version of OpenSSL with cryptography. +If you want to use cryptography with your own build of OpenSSL you will need to +make sure that the build is configured correctly so that your version of +OpenSSL doesn't conflict with Python's. + +The options you need to add allow the linker to identify every symbol correctly +even when multiple versions of the library are linked into the same program. If +you are using your distribution's source packages these will probably be +patched in for you already, otherwise you'll need to use options something like +this when configuring OpenSSL: + +.. code-block:: console + + $ ./config -Wl,--version-script=openssl.ld -Wl,-Bsymbolic-functions -fPIC shared + +You'll also need to generate your own ``openssl.ld`` file. For example:: + + OPENSSL_1.0.1F_CUSTOM { + global: + *; + }; + +You should replace the version string on the first line as appropriate for your +build. + +Using your own OpenSSL on OS X +------------------------------ + +To link cryptography against a custom version of OpenSSL you'll need to set +``ARCHFLAGS``, ``LDFLAGS``, and ``CFLAGS``. OpenSSL can be installed via +`Homebrew`_: + +.. code-block:: console + + $ brew install openssl + +Then install cryptography linking against the brewed version: + +.. code-block:: console + + $ env ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography + + +.. _`Homebrew`: http://brew.sh +.. _`pre-compiled binaries`: https://www.openssl.org/related/binaries.html -- cgit v1.2.3 From e51a2db0def7b8f01b5a7ce96f35eb07b4d14599 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 11:49:35 -0600 Subject: document HashAlgorithm --- docs/hazmat/primitives/interfaces.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 2adad913..f31e9f4a 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -204,6 +204,34 @@ Asymmetric Interfaces The public exponent. Alias for :attr:`public_exponent`. +Hash Algorithms +~~~~~~~~~~~~~~~ + +.. class:: HashAlgorithm + + .. versionadded:: 0.2 + + .. attribute:: name + + :type: str + + The standard name for the hash algorithm, for example: ``sha1`` or + ``sha256``. + + .. attribute:: digest_size + + :type: int + + The size of the resulting digest in bytes. + + .. attribute:: block_size + + :type: int + + The internal block size of the hash algorithm in bytes. + + + Key Derivation Functions ~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 1d65a2fb2e559852e6e40b7230ca68094cb88571 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 11:50:45 -0600 Subject: fix spacing, remove versionadded since HashAlgorithm was in 0.1 --- docs/hazmat/primitives/interfaces.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index f31e9f4a..cf7d5bfe 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -209,8 +209,6 @@ Hash Algorithms .. class:: HashAlgorithm - .. versionadded:: 0.2 - .. attribute:: name :type: str @@ -231,7 +229,6 @@ Hash Algorithms The internal block size of the hash algorithm in bytes. - Key Derivation Functions ~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 8377504228b28282e5bd4080048d36055f93a75d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 29 Jan 2014 10:16:24 -0800 Subject: Expose this method because probably someone will need it eventually --- cryptography/hazmat/bindings/openssl/x509.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cryptography/hazmat/bindings/openssl/x509.py b/cryptography/hazmat/bindings/openssl/x509.py index d492ea01..e4021a12 100644 --- a/cryptography/hazmat/bindings/openssl/x509.py +++ b/cryptography/hazmat/bindings/openssl/x509.py @@ -166,6 +166,7 @@ int X509_set_serialNumber(X509 *, ASN1_INTEGER *); X509_STORE *X509_STORE_new(void); void X509_STORE_free(X509_STORE *); int X509_STORE_add_cert(X509_STORE *, X509 *); +int X509_verify_cert(X509_STORE_CTX *); """ MACROS = """ -- cgit v1.2.3 From 4c75a8c34610aef42e3bf3635a393d14a55273f8 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 12:20:37 -0600 Subject: quotes inside, diff examples --- docs/hazmat/primitives/interfaces.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index cf7d5bfe..09a5a4ce 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -213,8 +213,8 @@ Hash Algorithms :type: str - The standard name for the hash algorithm, for example: ``sha1`` or - ``sha256``. + The standard name for the hash algorithm, for example: ``"sha256"`` or + ``"whirlpool"``. .. attribute:: digest_size -- cgit v1.2.3 From 831c04b669a8a2723c92407c029909850365db09 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 12:54:53 -0600 Subject: called -> used --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index 2c8dd541..c08e7d9a 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -44,7 +44,7 @@ class PBKDF2HMAC(object): def derive(self, key_material): if self._used: - raise AlreadyFinalized("PBKDF2 instances can only be called once") + raise AlreadyFinalized("PBKDF2 instances can only be used once") self._used = True if isinstance(key_material, six.text_type): -- cgit v1.2.3 From b0293bf7ed069fc1469d098cc71b9c50b9080d25 Mon Sep 17 00:00:00 2001 From: skeuomorf Date: Wed, 29 Jan 2014 21:41:02 +0200 Subject: Added installation section to index.rst --- docs/index.rst | 18 +++++++++++------- docs/installation.rst | 24 +++++++++++------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 73c40bf0..93b25ecf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,6 +5,16 @@ Welcome to ``cryptography`` primitives. We hope it'll be your one-stop-shop for all your cryptographic needs in Python. +Installation +------------ +You can install ``cryptography`` with ``pip``: + +.. code-block:: console + + $ pip install cryptography + +see :doc:`Installation` for more information + Why a new crypto library for Python? ------------------------------------ @@ -40,13 +50,6 @@ admonition at the top. We recommend using the recipes layer whenever possible, and falling back to the hazmat layer only when necessary. -Installation -~~~~~~~~~~~~ -.. toctree:: - :maxdepth: 2 - - installation - The recipes layer ~~~~~~~~~~~~~~~~~ @@ -73,6 +76,7 @@ The ``cryptography`` open source project .. toctree:: :maxdepth: 2 + installation contributing security api-stability diff --git a/docs/installation.rst b/docs/installation.rst index d9c30801..2206107e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -11,19 +11,17 @@ Installation Notes ================== On Windows ---------- -.. note:: - - If you're on Windows you'll need to make sure you have OpenSSL installed. - There are `pre-compiled binaries`_ available. If your installation is in - an unusual location set the ``LIB`` and ``INCLUDE`` environment variables - to include the corresponding locations. For example: - - .. code-block:: console - - C:\> \path\to\vcvarsall.bat x86_amd64 - C:\> set LIB=C:\OpenSSL-1.0.1f-64bit\lib;%LIB% - C:\> set INCLUDE=C:\OpenSSL-1.0.1f-64bit\include;%INCLUDE% - C:\> pip install cryptography +If you're on Windows you'll need to make sure you have OpenSSL installed. +There are `pre-compiled binaries`_ available. If your installation is in +an unusual location set the ``LIB`` and ``INCLUDE`` environment variables +to include the corresponding locations. For example: + +.. code-block:: console + + C:\> \path\to\vcvarsall.bat x86_amd64 + C:\> set LIB=C:\OpenSSL-1.0.1f-64bit\lib;%LIB% + C:\> set INCLUDE=C:\OpenSSL-1.0.1f-64bit\include;%INCLUDE% + C:\> pip install cryptography Using your own OpenSSL on Linux ------------------------------- -- cgit v1.2.3 From 6fb1a5a99d3742763961d907c9f297f89f2f0b91 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 13:44:07 -0600 Subject: add test for null char replacement --- tests/test_utils.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_utils.py b/tests/test_utils.py index f852f3ab..8ecb33f9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -180,6 +180,25 @@ def test_load_nist_vectors(): ] +def test_load_nist_vectors_with_null_chars(): + vector_data = textwrap.dedent(""" + COUNT = 0 + KEY = thing\\0withnulls + + COUNT = 1 + KEY = 00000000000000000000000000000000 + """).splitlines() + + assert load_nist_vectors(vector_data) == [ + { + "key": b"thing\x00withnulls", + }, + { + "key": b"00000000000000000000000000000000", + }, + ] + + def test_load_cryptrec_vectors(): vector_data = textwrap.dedent(""" # Vectors taken from http://info.isl.ntt.co.jp/crypt/eng/camellia/ -- cgit v1.2.3 From c58b478530a93df90d0c612df259d1668cdd3f6b Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 13:56:25 -0600 Subject: update docs re: PBKDF2HMAC iterations --- docs/hazmat/backends/interfaces.rst | 4 +++- docs/hazmat/primitives/key-derivation-functions.rst | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index ca3a5433..5b3e852a 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -163,7 +163,9 @@ A specific ``backend`` may provide one or more of these interfaces. :param bytes salt: A salt. :param int iterations: The number of iterations to perform of the hash - function. + function. This can be used to control the length of time the + operation takes. Higher numbers help mitigate brute force attacks + against derived keys. :param bytes key_material: The key material to use as a basis for the derived key. This is typically a password. diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index e652ecbf..bf069faa 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -58,7 +58,9 @@ key stretching) so match your needs to their capabilities. :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or longer. :param int iterations: The number of iterations to perform of the hash - function. See OWASP's `Password Storage Cheat Sheet`_ for more + function. This can be used to control the length of time the operation + takes. Higher numbers help mitigate brute force attacks against derived + keys. See OWASP's `Password Storage Cheat Sheet`_ for more detailed recommendations if you intend to use this for password storage. :param backend: A :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` -- cgit v1.2.3 From 1cab104d7c95aae20bd6068c5cb54f4dce149d91 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 14:30:11 -0600 Subject: expand docs to talk more about the purposes of KDFs --- .../hazmat/primitives/key-derivation-functions.rst | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index bf069faa..56c3a2bd 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -6,9 +6,24 @@ Key Derivation Functions .. currentmodule:: cryptography.hazmat.primitives.kdf Key derivation functions derive key material from passwords or other data -sources using a pseudo-random function (PRF). Each KDF is suitable for -different tasks (cryptographic key derivation, password storage, -key stretching) so match your needs to their capabilities. +sources using a pseudo-random function (PRF). Different KDFs are suitable for +different tasks such as: + +- Cryptographic key derivation + + Deriving a key suitable for use as input to an encryption algorithm. + Typically this means taking a password and running it through an algorithm + such as :class:`~cryptography.hazmat.primitives.kdf.PBKDF2HMAC` or HKDF. + This process is typically known as `key stretching`_. + +- Password storage + + When storing passwords you want to use an algorithm that is computationally + intensive. Legitimate users will only need to compute it once (for example, + taking the user's password, running it through the KDF, then comparing it + to the stored value), while attackers will need to do it billions of times. + Ideal password storage KDFs will be demanding on both computational and + memory resources. .. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend): @@ -17,7 +32,7 @@ key stretching) so match your needs to their capabilities. `PBKDF2`_ (Password Based Key Derivation Function 2) is typically used for deriving a cryptographic key from a password. It may also be used for key storage, but an alternate key storage KDF such as `scrypt` is generally - considered a better solution since it is designed to be slow. + considered a better solution. This class conforms to the :class:`~cryptography.hazmat.primitives.interfaces.KeyDerivationFunction` @@ -105,3 +120,4 @@ key stretching) so match your needs to their capabilities. .. _`Password Storage Cheat Sheet`: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet .. _`PBKDF2`: http://en.wikipedia.org/wiki/PBKDF2 .. _`scrypt`: http://en.wikipedia.org/wiki/Scrypt +.. _`key stretching`: http://en.wikipedia.org/wiki/Key_stretching -- cgit v1.2.3 From fb042ad11a98f3a8eb7103d052e4d703687c8739 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 14:37:09 -0600 Subject: switch to private attributes in pbkdf2hmac --- cryptography/hazmat/primitives/kdf/pbkdf2.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index c08e7d9a..71b88211 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -31,7 +31,7 @@ class PBKDF2HMAC(object): algorithm.name) ) self._used = False - self.algorithm = algorithm + self._algorithm = algorithm self._length = length if isinstance(salt, six.text_type): raise TypeError( @@ -39,7 +39,7 @@ class PBKDF2HMAC(object): "material." ) self._salt = salt - self.iterations = iterations + self._iterations = iterations self._backend = backend def derive(self, key_material): @@ -53,10 +53,10 @@ class PBKDF2HMAC(object): "material." ) return self._backend.derive_pbkdf2_hmac( - self.algorithm, + self._algorithm, self._length, self._salt, - self.iterations, + self._iterations, key_material ) -- cgit v1.2.3 From 446cc2ab9138811a464175767ef30bb110035ff4 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 14:39:30 -0600 Subject: a few typo fixes, capitalization, etc --- docs/hazmat/backends/interfaces.rst | 8 ++++---- docs/index.rst | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 11e2f2a2..dc24f84d 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -37,7 +37,7 @@ A specific ``backend`` may provide one or more of these interfaces. .. method:: create_symmetric_encryption_ctx(cipher, mode) Create a - :class:`~cryptogrpahy.hazmat.primitives.interfaces.CipherContext` that + :class:`~cryptography.hazmat.primitives.interfaces.CipherContext` that can be used for encrypting data with the symmetric ``cipher`` using the given ``mode``. @@ -56,7 +56,7 @@ A specific ``backend`` may provide one or more of these interfaces. .. method:: create_symmetric_decryption_ctx(cipher, mode) Create a - :class:`~cryptogrpahy.hazmat.primitives.interfaces.CipherContext` that + :class:`~cryptography.hazmat.primitives.interfaces.CipherContext` that can be used for decrypting data with the symmetric ``cipher`` using the given ``mode``. @@ -91,7 +91,7 @@ A specific ``backend`` may provide one or more of these interfaces. .. method:: create_hash_ctx(algorithm) Create a - :class:`~cryptogrpahy.hazmat.primitives.interfaces.HashContext` that + :class:`~cryptography.hazmat.primitives.interfaces.HashContext` that uses the specified ``algorithm`` to calculate a message digest. :param algorithm: An instance of a @@ -121,7 +121,7 @@ A specific ``backend`` may provide one or more of these interfaces. .. method:: create_hmac_ctx(algorithm) Create a - :class:`~cryptogrpahy.hazmat.primitives.interfaces.HashContext` that + :class:`~cryptography.hazmat.primitives.interfaces.HashContext` that uses the specified ``algorithm`` to calculate a hash-based message authentication code. diff --git a/docs/index.rst b/docs/index.rst index 93b25ecf..d94e2fce 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,7 +13,7 @@ You can install ``cryptography`` with ``pip``: $ pip install cryptography -see :doc:`Installation` for more information +See :doc:`Installation` for more information. Why a new crypto library for Python? ------------------------------------ -- cgit v1.2.3 From f3b57e38ed3fd70dcb188a9eb6b84cf7cee01f85 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 14:45:10 -0600 Subject: one more style fix --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index d94e2fce..86cd42c6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,7 +13,7 @@ You can install ``cryptography`` with ``pip``: $ pip install cryptography -See :doc:`Installation` for more information. +See :doc:`Installation ` for more information. Why a new crypto library for Python? ------------------------------------ -- cgit v1.2.3 From 0b181182aef574c436a92a175937af32e54a2378 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 16:34:47 -0600 Subject: a bit more language work + changelog changes for pbkdf2hmac --- docs/changelog.rst | 2 +- docs/hazmat/primitives/key-derivation-functions.rst | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index be42b5db..f401fe7c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,7 +14,7 @@ Changelog * Improved thread-safety for the OpenSSL backend. * Fixed compilation on systems where OpenSSL's ``ec.h`` header is not available, such as CentOS. -* Added PBKDF2HMAC support to OpenSSL backend. +* Added :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 56c3a2bd..529f4416 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -5,18 +5,18 @@ Key Derivation Functions .. currentmodule:: cryptography.hazmat.primitives.kdf -Key derivation functions derive key material from passwords or other data -sources using a pseudo-random function (PRF). Different KDFs are suitable for -different tasks such as: +Key derivation functions derive bytes suitable for cryptographic operations +from passwords or other data sources using a pseudo-random function (PRF). +Different KDFs are suitable for different tasks such as: -- Cryptographic key derivation +* Cryptographic key derivation Deriving a key suitable for use as input to an encryption algorithm. Typically this means taking a password and running it through an algorithm - such as :class:`~cryptography.hazmat.primitives.kdf.PBKDF2HMAC` or HKDF. + such as :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC` or HKDF. This process is typically known as `key stretching`_. -- Password storage +* Password storage When storing passwords you want to use an algorithm that is computationally intensive. Legitimate users will only need to compute it once (for example, @@ -25,13 +25,15 @@ different tasks such as: Ideal password storage KDFs will be demanding on both computational and memory resources. -.. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend): +.. currentmodule:: cryptography.hazmat.primitives.kdf.pbkdf2 + +.. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend) .. versionadded:: 0.2 `PBKDF2`_ (Password Based Key Derivation Function 2) is typically used for deriving a cryptographic key from a password. It may also be used for - key storage, but an alternate key storage KDF such as `scrypt` is generally + key storage, but an alternate key storage KDF such as `scrypt`_ is generally considered a better solution. This class conforms to the @@ -85,7 +87,7 @@ different tasks such as: :param key_material bytes: The input key material. For PBKDF2 this should be a password. - :return: The new key. + :return bytes: the derived key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is -- cgit v1.2.3 From 84fc58c1d03e6512c203ba19439cebc17fd393a7 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 17:16:27 -0600 Subject: simplify check for algorithm --- cryptography/hazmat/backends/commoncrypto/backend.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index ce40bb38..8792c8ac 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -144,12 +144,7 @@ class Backend(object): return _CipherContext(self, cipher, mode, self._lib.kCCDecrypt) def pbkdf2_hmac_supported(self, algorithm): - try: - self._supported_pbkdf2_hmac_algorithms[algorithm.name] - except KeyError: - return False - else: - return True + return algorithm.name in self._supported_pbkdf2_hmac_algorithms def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, key_material): -- cgit v1.2.3 From e0608db24e4744fd0aeb83a52d173e66989cd54c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 17:17:44 -0600 Subject: simplify hmac supported and hash supported calls for commoncrypto --- cryptography/hazmat/backends/commoncrypto/backend.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 4e70cab5..b612a805 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -90,20 +90,10 @@ class Backend(object): } def hash_supported(self, algorithm): - try: - self._hash_mapping[algorithm.name] - except KeyError: - return False - else: - return True + return algorithm.name in self._hash_mapping def hmac_supported(self, algorithm): - try: - self._supported_hmac_algorithms[algorithm.name] - except KeyError: - return False - else: - return True + return algorithm.name in self._supported_hmac_algorithms def create_hash_ctx(self, algorithm): return _HashContext(self, algorithm) -- cgit v1.2.3 From ef9d65a808d5afef3f7824cf0300aac21e7c698f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 17:20:10 -0600 Subject: one more replacement --- cryptography/hazmat/backends/commoncrypto/backend.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index b612a805..6a986090 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -102,11 +102,7 @@ class Backend(object): return _HMACContext(self, key, algorithm) def cipher_supported(self, cipher, mode): - try: - self._cipher_registry[type(cipher), type(mode)] - except KeyError: - return False - return True + return (type(cipher), type(mode)) in self._cipher_registry def create_symmetric_encryption_ctx(self, cipher, mode): if isinstance(mode, GCM): -- cgit v1.2.3 From 15a86a01dcbf3fc1209a7e44687aeb8c510b37b4 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 29 Jan 2014 17:44:47 -0600 Subject: PBKDF2HMAC requires a PBKDF2HMACBackend provider. I cannot be trusted with a pasteboard --- docs/hazmat/primitives/key-derivation-functions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 529f4416..551dbd6d 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -80,7 +80,7 @@ Different KDFs are suitable for different tasks such as: keys. See OWASP's `Password Storage Cheat Sheet`_ for more detailed recommendations if you intend to use this for password storage. :param backend: A - :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` provider. .. method:: derive(key_material) -- cgit v1.2.3