Implementing shake_128 and shake_256 in the hashlib module.

This commit is contained in:
Charles Hubain
2023-02-26 12:35:59 +09:00
parent 003f3b4fe1
commit 29de419c87
2 changed files with 210 additions and 167 deletions

View File

@@ -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)

View File

@@ -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<NewHashArgs> 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<NewHashArgs> 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> {
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<HashWrapper>,
ctx: PyRwLock<HashWrapper>,
}
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::<Md5>()).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<HashXofWrapper>,
}
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<u8> {
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<PyBytes> {
Ok(self.ctx.read().finalize_xof(args.length(vm)?).into())
}
#[pymethod]
fn hexdigest(&self, args: XofDigestArgs, vm: &VirtualMachine) -> PyResult<String> {
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<PyHasher> {
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<PyObjectRef> {
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<ArgBytesLike>) -> PyResult<PyHasher> {
if let OptionalArg::Present(data) = data {
hasher.update(data);
}
Ok(hasher)
#[pyfunction]
fn md5(args: HashArgs) -> PyHasher {
PyHasher::new("md5", HashWrapper::new::<Md5>(args.string))
}
#[pyfunction]
fn md5(args: HashArgs) -> PyResult<PyHasher> {
init(PyHasher::new("md5", HashWrapper::new::<Md5>()), args.string)
fn sha1(args: HashArgs) -> PyHasher {
PyHasher::new("sha1", HashWrapper::new::<Sha1>(args.string))
}
#[pyfunction]
fn sha1(args: HashArgs) -> PyResult<PyHasher> {
init(
PyHasher::new("sha1", HashWrapper::new::<Sha1>()),
args.string,
)
fn sha224(args: HashArgs) -> PyHasher {
PyHasher::new("sha224", HashWrapper::new::<Sha224>(args.string))
}
#[pyfunction]
fn sha224(args: HashArgs) -> PyResult<PyHasher> {
init(
PyHasher::new("sha224", HashWrapper::new::<Sha224>()),
args.string,
)
fn sha256(args: HashArgs) -> PyHasher {
PyHasher::new("sha256", HashWrapper::new::<Sha256>(args.string))
}
#[pyfunction]
fn sha256(args: HashArgs) -> PyResult<PyHasher> {
init(
PyHasher::new("sha256", HashWrapper::new::<Sha256>()),
args.string,
)
fn sha384(args: HashArgs) -> PyHasher {
PyHasher::new("sha384", HashWrapper::new::<Sha384>(args.string))
}
#[pyfunction]
fn sha384(args: HashArgs) -> PyResult<PyHasher> {
init(
PyHasher::new("sha384", HashWrapper::new::<Sha384>()),
args.string,
)
fn sha512(args: HashArgs) -> PyHasher {
PyHasher::new("sha512", HashWrapper::new::<Sha512>(args.string))
}
#[pyfunction]
fn sha512(args: HashArgs) -> PyResult<PyHasher> {
init(
PyHasher::new("sha512", HashWrapper::new::<Sha512>()),
args.string,
)
fn sha3_224(args: HashArgs) -> PyHasher {
PyHasher::new("sha3_224", HashWrapper::new::<Sha3_224>(args.string))
}
#[pyfunction]
fn sha3_224(args: HashArgs) -> PyResult<PyHasher> {
init(
PyHasher::new("sha3_224", HashWrapper::new::<Sha3_224>()),
args.string,
)
fn sha3_256(args: HashArgs) -> PyHasher {
PyHasher::new("sha3_256", HashWrapper::new::<Sha3_256>(args.string))
}
#[pyfunction]
fn sha3_256(args: HashArgs) -> PyResult<PyHasher> {
init(
PyHasher::new("sha3_256", HashWrapper::new::<Sha3_256>()),
args.string,
)
fn sha3_384(args: HashArgs) -> PyHasher {
PyHasher::new("sha3_384", HashWrapper::new::<Sha3_384>(args.string))
}
#[pyfunction]
fn sha3_384(args: HashArgs) -> PyResult<PyHasher> {
init(
PyHasher::new("sha3_384", HashWrapper::new::<Sha3_384>()),
args.string,
)
fn sha3_512(args: HashArgs) -> PyHasher {
PyHasher::new("sha3_512", HashWrapper::new::<Sha3_512>(args.string))
}
#[pyfunction]
fn sha3_512(args: HashArgs) -> PyResult<PyHasher> {
init(
PyHasher::new("sha3_512", HashWrapper::new::<Sha3_512>()),
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<PyHasher> {
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<PyHasher> {
Err(vm.new_not_implemented_error("shake_256".to_owned()))
fn blake2b(args: BlakeHashArgs) -> PyHasher {
PyHasher::new("blake2b", HashWrapper::new::<Blake2b512>(args.data))
}
#[pyfunction]
fn blake2b(args: BlakeHashArgs) -> PyResult<PyHasher> {
// TODO: handle parameters
init(
PyHasher::new("blake2b", HashWrapper::new::<Blake2b512>()),
args.data,
)
}
#[pyfunction]
fn blake2s(args: BlakeHashArgs) -> PyResult<PyHasher> {
// TODO: handle parameters
init(
PyHasher::new("blake2s", HashWrapper::new::<Blake2s256>()),
args.data,
)
fn blake2s(args: BlakeHashArgs) -> PyHasher {
PyHasher::new("blake2s", HashWrapper::new::<Blake2s256>(args.data))
}
trait ThreadSafeDynDigest: DynClone + DynDigest + Sync + Send {}
@@ -305,17 +310,21 @@ mod hashlib {
}
impl HashWrapper {
fn new<D>() -> Self
fn new<D>(data: OptionalArg<ArgBytesLike>) -> Self
where
D: ThreadSafeDynDigest + BlockSizeUser + Default + 'static,
{
HashWrapper {
let mut h = HashWrapper {
block_size: D::block_size(),
inner: Box::<D>::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<u8> {
fn finalize(&self) -> Vec<u8> {
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<ArgBytesLike>) -> 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<ArgBytesLike>) -> 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<u8> {
match self {
HashXofWrapper::Shake128(h) => h.clone().finalize_boxed(length).into_vec(),
HashXofWrapper::Shake256(h) => h.clone().finalize_boxed(length).into_vec(),
}
}
}
}