diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index d9a3bd6ca..5c2663e8e 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -102,8 +102,7 @@ class HashLibTestCase(unittest.TestCase): 'sha384', 'SHA384', 'sha512', 'SHA512', 'blake2b', 'blake2s', 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512', - # TODO: RUSTPYTHON - # 'shake_128', 'shake_256' + 'shake_128', 'shake_256' ) shakes = {'shake_128', 'shake_256'} @@ -260,8 +259,6 @@ class HashLibTestCase(unittest.TestCase): self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam') self.assertRaises(TypeError, hashlib.new, 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_new_upper_to_lower(self): self.assertEqual(hashlib.new("SHA256").name, "sha256") @@ -303,6 +300,8 @@ class HashLibTestCase(unittest.TestCase): self.assertIsInstance(h.digest(), bytes) self.assertEqual(hexstr(h.digest()), h.hexdigest()) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_digest_length_overflow(self): # See issue #34922 large_sizes = (2**29, 2**32-10, 2**32+10, 2**61, 2**64-10, 2**64+10) @@ -404,8 +403,6 @@ class HashLibTestCase(unittest.TestCase): self.check_no_unicode('blake2b') self.check_no_unicode('blake2s') - # TODO: RUSTPYTHON - @unittest.expectedFailure @requires_sha3 def test_no_unicode_sha3(self): self.check_no_unicode('sha3_224') @@ -851,30 +848,22 @@ class HashLibTestCase(unittest.TestCase): for msg, md in read_vectors('sha3_512'): self.check('sha3_512', msg, md) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_case_shake_128_0(self): self.check('shake_128', b"", "7f9c2ba4e88f827d616045507605853ed73b8093f6efbc88eb1a6eacfa66ef26", True) self.check('shake_128', b"", "7f9c", True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_case_shake128_vector(self): for msg, md in read_vectors('shake_128'): self.check('shake_128', msg, md, True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_case_shake_256_0(self): self.check('shake_256', b"", "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f", True) self.check('shake_256', b"", "46b9", True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_case_shake256_vector(self): for msg, md in read_vectors('shake_256'): self.check('shake_256', msg, md, True) diff --git a/stdlib/src/hashlib.rs b/stdlib/src/hashlib.rs index 3f69a0478..575197433 100644 --- a/stdlib/src/hashlib.rs +++ b/stdlib/src/hashlib.rs @@ -2,19 +2,20 @@ pub(crate) use hashlib::make_module; #[pymodule] mod hashlib { - use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; + use crate::common::lock::PyRwLock; use crate::vm::{ builtins::{PyBytes, PyStrRef, PyTypeRef}, function::{ArgBytesLike, FuncArgs, OptionalArg}, - PyPayload, PyResult, VirtualMachine, + PyObjectRef, PyPayload, PyResult, VirtualMachine, }; use blake2::{Blake2b512, Blake2s256}; use digest::{core_api::BlockSizeUser, DynDigest}; + use digest::{ExtendableOutput, Update}; use dyn_clone::{clone_trait_object, DynClone}; use md5::Md5; use sha1::Sha1; use sha2::{Sha224, Sha256, Sha384, Sha512}; - use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; // TODO: , shake_128, shake_256; + use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}; #[derive(FromArgs)] #[allow(unused)] @@ -36,6 +37,15 @@ mod hashlib { usedforsecurity: bool, } + impl From for BlakeHashArgs { + fn from(args: NewHashArgs) -> Self { + Self { + data: args.data, + usedforsecurity: args.usedforsecurity, + } + } + } + #[derive(FromArgs)] #[allow(unused)] struct HashArgs { @@ -45,17 +55,40 @@ mod hashlib { usedforsecurity: bool, } + impl From for HashArgs { + fn from(args: NewHashArgs) -> Self { + Self { + string: args.data, + usedforsecurity: args.usedforsecurity, + } + } + } + + #[derive(FromArgs)] + #[allow(unused)] + struct XofDigestArgs { + #[pyarg(positional)] + length: isize, + } + + impl XofDigestArgs { + fn length(&self, vm: &VirtualMachine) -> PyResult { + usize::try_from(self.length) + .map_err(|_| vm.new_value_error("length must be non-negative".to_owned())) + } + } + #[pyattr] - #[pyclass(module = "hashlib", name = "hasher")] + #[pyclass(module = "hashlib", name = "HASH")] #[derive(PyPayload)] struct PyHasher { name: String, - buffer: PyRwLock, + ctx: PyRwLock, } impl std::fmt::Debug for PyHasher { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "hasher {}", self.name) + write!(f, "HASH {}", self.name) } } @@ -64,21 +97,13 @@ mod hashlib { fn new(name: &str, d: HashWrapper) -> Self { PyHasher { name: name.to_owned(), - buffer: PyRwLock::new(d), + ctx: PyRwLock::new(d), } } - fn read(&self) -> PyRwLockReadGuard<'_, HashWrapper> { - self.buffer.read() - } - - fn write(&self) -> PyRwLockWriteGuard<'_, HashWrapper> { - self.buffer.write() - } - #[pyslot] fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("md5", HashWrapper::new::()).into_pyobject(vm)) + Err(vm.new_type_error("cannot create 'hashlib.HASH' instances".into())) } #[pygetset] @@ -88,208 +113,188 @@ mod hashlib { #[pygetset] fn digest_size(&self) -> usize { - self.read().digest_size() + self.ctx.read().digest_size() } #[pygetset] fn block_size(&self) -> usize { - self.read().block_size() + self.ctx.read().block_size() } #[pymethod] fn update(&self, data: ArgBytesLike) { - data.with_ref(|bytes| self.write().input(bytes)); + data.with_ref(|bytes| self.ctx.write().update(bytes)); } #[pymethod] fn digest(&self) -> PyBytes { - self.get_digest().into() + self.ctx.read().finalize().into() } #[pymethod] fn hexdigest(&self) -> String { - let result = self.get_digest(); - hex::encode(result) + hex::encode(self.ctx.read().finalize()) } #[pymethod] fn copy(&self) -> Self { - PyHasher::new(&self.name, self.buffer.read().clone()) + PyHasher::new(&self.name, self.ctx.read().clone()) + } + } + + #[pyattr] + #[pyclass(module = "hashlib", name = "HASHXOF")] + #[derive(PyPayload)] + struct PyHasherXof { + name: String, + ctx: PyRwLock, + } + + impl std::fmt::Debug for PyHasherXof { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "HASHXOF {}", self.name) + } + } + + #[pyclass] + impl PyHasherXof { + fn new(name: &str, d: HashXofWrapper) -> Self { + PyHasherXof { + name: name.to_owned(), + ctx: PyRwLock::new(d), + } } - fn get_digest(&self) -> Vec { - self.read().get_digest() + #[pyslot] + fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("cannot create 'hashlib.HASHXOF' instances".into())) + } + + #[pygetset] + fn name(&self) -> String { + self.name.clone() + } + + #[pygetset] + fn digest_size(&self) -> usize { + 0 + } + + #[pygetset] + fn block_size(&self) -> usize { + self.ctx.read().block_size() + } + + #[pymethod] + fn update(&self, data: ArgBytesLike) { + data.with_ref(|bytes| self.ctx.write().update(bytes)); + } + + #[pymethod] + fn digest(&self, args: XofDigestArgs, vm: &VirtualMachine) -> PyResult { + Ok(self.ctx.read().finalize_xof(args.length(vm)?).into()) + } + + #[pymethod] + fn hexdigest(&self, args: XofDigestArgs, vm: &VirtualMachine) -> PyResult { + Ok(hex::encode(self.ctx.read().finalize_xof(args.length(vm)?))) + } + + #[pymethod] + fn copy(&self) -> Self { + PyHasherXof::new(&self.name, self.ctx.read().clone()) } } #[pyfunction(name = "new")] - fn hashlib_new(args: NewHashArgs, vm: &VirtualMachine) -> PyResult { - match args.name.as_str() { - "md5" => md5(HashArgs { - string: args.data, - usedforsecurity: args.usedforsecurity, - }), - "sha1" => sha1(HashArgs { - string: args.data, - usedforsecurity: args.usedforsecurity, - }), - "sha224" => sha224(HashArgs { - string: args.data, - usedforsecurity: args.usedforsecurity, - }), - "sha256" => sha256(HashArgs { - string: args.data, - usedforsecurity: args.usedforsecurity, - }), - "sha384" => sha384(HashArgs { - string: args.data, - usedforsecurity: args.usedforsecurity, - }), - "sha512" => sha512(HashArgs { - string: args.data, - usedforsecurity: args.usedforsecurity, - }), - "sha3_224" => sha3_224(HashArgs { - string: args.data, - usedforsecurity: args.usedforsecurity, - }), - "sha3_256" => sha3_256(HashArgs { - string: args.data, - usedforsecurity: args.usedforsecurity, - }), - "sha3_384" => sha3_384(HashArgs { - string: args.data, - usedforsecurity: args.usedforsecurity, - }), - "sha3_512" => sha3_512(HashArgs { - string: args.data, - usedforsecurity: args.usedforsecurity, - }), - // TODO: "shake_128" => shake_128(args.data, ), - // TODO: "shake_256" => shake_256(args.data, ), - "blake2b" => blake2b(BlakeHashArgs { - data: args.data, - usedforsecurity: args.usedforsecurity, - }), - "blake2s" => blake2s(BlakeHashArgs { - data: args.data, - usedforsecurity: args.usedforsecurity, - }), + fn hashlib_new(args: NewHashArgs, vm: &VirtualMachine) -> PyResult { + match args.name.as_str().to_lowercase().as_str() { + "md5" => Ok(md5(args.into()).into_pyobject(vm)), + "sha1" => Ok(sha1(args.into()).into_pyobject(vm)), + "sha224" => Ok(sha224(args.into()).into_pyobject(vm)), + "sha256" => Ok(sha256(args.into()).into_pyobject(vm)), + "sha384" => Ok(sha384(args.into()).into_pyobject(vm)), + "sha512" => Ok(sha512(args.into()).into_pyobject(vm)), + "sha3_224" => Ok(sha3_224(args.into()).into_pyobject(vm)), + "sha3_256" => Ok(sha3_256(args.into()).into_pyobject(vm)), + "sha3_384" => Ok(sha3_384(args.into()).into_pyobject(vm)), + "sha3_512" => Ok(sha3_512(args.into()).into_pyobject(vm)), + "shake_128" => Ok(shake_128(args.into()).into_pyobject(vm)), + "shake_256" => Ok(shake_256(args.into()).into_pyobject(vm)), + "blake2b" => Ok(blake2b(args.into()).into_pyobject(vm)), + "blake2s" => Ok(blake2s(args.into()).into_pyobject(vm)), other => Err(vm.new_value_error(format!("Unknown hashing algorithm: {other}"))), } } - fn init(hasher: PyHasher, data: OptionalArg) -> PyResult { - if let OptionalArg::Present(data) = data { - hasher.update(data); - } - - Ok(hasher) + #[pyfunction] + fn md5(args: HashArgs) -> PyHasher { + PyHasher::new("md5", HashWrapper::new::(args.string)) } #[pyfunction] - fn md5(args: HashArgs) -> PyResult { - init(PyHasher::new("md5", HashWrapper::new::()), args.string) + fn sha1(args: HashArgs) -> PyHasher { + PyHasher::new("sha1", HashWrapper::new::(args.string)) } #[pyfunction] - fn sha1(args: HashArgs) -> PyResult { - init( - PyHasher::new("sha1", HashWrapper::new::()), - args.string, - ) + fn sha224(args: HashArgs) -> PyHasher { + PyHasher::new("sha224", HashWrapper::new::(args.string)) } #[pyfunction] - fn sha224(args: HashArgs) -> PyResult { - init( - PyHasher::new("sha224", HashWrapper::new::()), - args.string, - ) + fn sha256(args: HashArgs) -> PyHasher { + PyHasher::new("sha256", HashWrapper::new::(args.string)) } #[pyfunction] - fn sha256(args: HashArgs) -> PyResult { - init( - PyHasher::new("sha256", HashWrapper::new::()), - args.string, - ) + fn sha384(args: HashArgs) -> PyHasher { + PyHasher::new("sha384", HashWrapper::new::(args.string)) } #[pyfunction] - fn sha384(args: HashArgs) -> PyResult { - init( - PyHasher::new("sha384", HashWrapper::new::()), - args.string, - ) + fn sha512(args: HashArgs) -> PyHasher { + PyHasher::new("sha512", HashWrapper::new::(args.string)) } #[pyfunction] - fn sha512(args: HashArgs) -> PyResult { - init( - PyHasher::new("sha512", HashWrapper::new::()), - args.string, - ) + fn sha3_224(args: HashArgs) -> PyHasher { + PyHasher::new("sha3_224", HashWrapper::new::(args.string)) } #[pyfunction] - fn sha3_224(args: HashArgs) -> PyResult { - init( - PyHasher::new("sha3_224", HashWrapper::new::()), - args.string, - ) + fn sha3_256(args: HashArgs) -> PyHasher { + PyHasher::new("sha3_256", HashWrapper::new::(args.string)) } #[pyfunction] - fn sha3_256(args: HashArgs) -> PyResult { - init( - PyHasher::new("sha3_256", HashWrapper::new::()), - args.string, - ) + fn sha3_384(args: HashArgs) -> PyHasher { + PyHasher::new("sha3_384", HashWrapper::new::(args.string)) } #[pyfunction] - fn sha3_384(args: HashArgs) -> PyResult { - init( - PyHasher::new("sha3_384", HashWrapper::new::()), - args.string, - ) + fn sha3_512(args: HashArgs) -> PyHasher { + PyHasher::new("sha3_512", HashWrapper::new::(args.string)) } #[pyfunction] - fn sha3_512(args: HashArgs) -> PyResult { - init( - PyHasher::new("sha3_512", HashWrapper::new::()), - args.string, - ) + fn shake_128(args: HashArgs) -> PyHasherXof { + PyHasherXof::new("shake_128", HashXofWrapper::new_shake_128(args.string)) } #[pyfunction] - fn shake_128(_args: HashArgs, vm: &VirtualMachine) -> PyResult { - Err(vm.new_not_implemented_error("shake_256".to_owned())) + fn shake_256(args: HashArgs) -> PyHasherXof { + PyHasherXof::new("shake_256", HashXofWrapper::new_shake_256(args.string)) } #[pyfunction] - fn shake_256(_args: HashArgs, vm: &VirtualMachine) -> PyResult { - Err(vm.new_not_implemented_error("shake_256".to_owned())) + fn blake2b(args: BlakeHashArgs) -> PyHasher { + PyHasher::new("blake2b", HashWrapper::new::(args.data)) } #[pyfunction] - fn blake2b(args: BlakeHashArgs) -> PyResult { - // TODO: handle parameters - init( - PyHasher::new("blake2b", HashWrapper::new::()), - args.data, - ) - } - - #[pyfunction] - fn blake2s(args: BlakeHashArgs) -> PyResult { - // TODO: handle parameters - init( - PyHasher::new("blake2s", HashWrapper::new::()), - args.data, - ) + fn blake2s(args: BlakeHashArgs) -> PyHasher { + PyHasher::new("blake2s", HashWrapper::new::(args.data)) } trait ThreadSafeDynDigest: DynClone + DynDigest + Sync + Send {} @@ -305,17 +310,21 @@ mod hashlib { } impl HashWrapper { - fn new() -> Self + fn new(data: OptionalArg) -> Self where D: ThreadSafeDynDigest + BlockSizeUser + Default + 'static, { - HashWrapper { + let mut h = HashWrapper { block_size: D::block_size(), inner: Box::::default(), + }; + if let OptionalArg::Present(d) = data { + d.with_ref(|bytes| h.update(bytes)); } + h } - fn input(&mut self, data: &[u8]) { + fn update(&mut self, data: &[u8]) { self.inner.update(data); } @@ -327,9 +336,54 @@ mod hashlib { self.inner.output_size() } - fn get_digest(&self) -> Vec { + fn finalize(&self) -> Vec { let cloned = self.inner.box_clone(); cloned.finalize().into_vec() } } + + #[derive(Clone)] + enum HashXofWrapper { + Shake128(Shake128), + Shake256(Shake256), + } + + impl HashXofWrapper { + fn new_shake_128(data: OptionalArg) -> Self { + let mut h = HashXofWrapper::Shake128(Shake128::default()); + if let OptionalArg::Present(d) = data { + d.with_ref(|bytes| h.update(bytes)); + } + h + } + + fn new_shake_256(data: OptionalArg) -> Self { + let mut h = HashXofWrapper::Shake256(Shake256::default()); + if let OptionalArg::Present(d) = data { + d.with_ref(|bytes| h.update(bytes)); + } + h + } + + fn update(&mut self, data: &[u8]) { + match self { + HashXofWrapper::Shake128(h) => h.update(data), + HashXofWrapper::Shake256(h) => h.update(data), + } + } + + fn block_size(&self) -> usize { + match self { + HashXofWrapper::Shake128(_) => Shake128::block_size(), + HashXofWrapper::Shake256(_) => Shake256::block_size(), + } + } + + fn finalize_xof(&self, length: usize) -> Vec { + match self { + HashXofWrapper::Shake128(h) => h.clone().finalize_boxed(length).into_vec(), + HashXofWrapper::Shake256(h) => h.clone().finalize_boxed(length).into_vec(), + } + } + } }