mirror of
https://github.com/denoland/deno.git
synced 2025-02-23 05:33:33 -05:00

Currently we are using the pure rust backend of `sha2`, which has subpar performance compared to asm implementations. We already depend on `ring`, so just use that instead of `sha2` for sha256/sha512 digests. This also speeds up things like S3 uploads, which calculate sha digests of the uploaded objects. On my local machine, this speeds up uploading a 100MB file (to a localhost s3 provider via`@aws-sdk/client-s3`) by about 2x <details> <summary>Benchmark:</summary> ```ts import { createHmac } from "node:crypto"; for ( const size of [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000] ) { const input = "a".repeat(size); Deno.bench({ name: `sha256-${size}`, fn() { const _hash = createHmac("sha256", input).update(input).digest(); }, }); Deno.bench({ name: `sha512-${size}`, fn() { const _hash = createHmac("sha512", input).update(input).digest(); }, }); } ``` </details> <details> <summary>Results (arm64 macOS):</summary> ``` --- sha256-1 --- ../../deno/target/release/deno 2.527 µs 1.240 times faster /Users/nathanwhit/.deno/bin/deno 3.132 µs --- sha512-1 --- ../../deno/target/release/deno 3.364 µs 1.071 times faster /Users/nathanwhit/.deno/bin/deno 3.603 µs --- sha256-10 --- ../../deno/target/release/deno 3.060 µs 1.027 times faster /Users/nathanwhit/.deno/bin/deno 3.144 µs --- sha512-10 --- ../../deno/target/release/deno 3.583 µs 1.047 times faster /Users/nathanwhit/.deno/bin/deno 3.751 µs --- sha256-100 --- ../../deno/target/release/deno 3.695 µs 1.244 times faster /Users/nathanwhit/.deno/bin/deno 4.598 µs --- sha512-100 --- ../../deno/target/release/deno 3.386 µs 1.188 times faster /Users/nathanwhit/.deno/bin/deno 4.021 µs --- sha256-1000 --- ../../deno/target/release/deno 4.007 µs 3.230 times faster /Users/nathanwhit/.deno/bin/deno 12.944 µs --- sha512-1000 --- ../../deno/target/release/deno 6.463 µs 1.466 times faster /Users/nathanwhit/.deno/bin/deno 9.477 µs --- sha256-10000 --- ../../deno/target/release/deno 11.674 µs 6.981 times faster /Users/nathanwhit/.deno/bin/deno 81.493 µs --- sha512-10000 --- ../../deno/target/release/deno 31.250 µs 1.740 times faster /Users/nathanwhit/.deno/bin/deno 54.364 µs --- sha256-100000 --- ../../deno/target/release/deno 82.800 µs 9.393 times faster /Users/nathanwhit/.deno/bin/deno 777.719 µs --- sha512-100000 --- ../../deno/target/release/deno 269.726 µs 1.851 times faster /Users/nathanwhit/.deno/bin/deno 499.243 µs --- sha256-1000000 --- ../../deno/target/release/deno 808.662 µs 9.427 times faster /Users/nathanwhit/.deno/bin/deno 7.623 ms --- sha512-1000000 --- ../../deno/target/release/deno 2.672 ms 1.795 times faster /Users/nathanwhit/.deno/bin/deno 4.795 ms --- sha256-10000000 --- ../../deno/target/release/deno 7.823 ms 9.868 times faster /Users/nathanwhit/.deno/bin/deno 77.201 ms --- sha512-10000000 --- ../../deno/target/release/deno 26.197 ms 1.846 times faster /Users/nathanwhit/.deno/bin/deno 48.356 ms ``` </details> <details> <summary>Results (x86_64 linux):</summary> ``` --- sha256-1 --- /home/nathanwhit/.deno/bin/deno 10.726 µs 1.229 times faster ../../../deno/target/release-lite/deno 13.184 µs --- sha512-1 --- /home/nathanwhit/.deno/bin/deno 13.177 µs 1.051 times faster ../../../deno/target/release-lite/deno 13.845 µs --- sha256-10 --- /home/nathanwhit/.deno/bin/deno 13.156 µs 1.047 times faster ../../../deno/target/release-lite/deno 13.780 µs --- sha512-10 --- /home/nathanwhit/.deno/bin/deno 14.386 µs 1.029 times faster ../../../deno/target/release-lite/deno 14.807 µs --- sha256-100 --- /home/nathanwhit/.deno/bin/deno 14.580 µs 1.083 times faster ../../../deno/target/release-lite/deno 15.789 µs --- sha512-100 --- /home/nathanwhit/.deno/bin/deno 13.477 µs 1.131 times faster ../../../deno/target/release-lite/deno 15.238 µs --- sha256-1000 --- ../../../deno/target/release-lite/deno 17.208 µs 1.116 times faster /home/nathanwhit/.deno/bin/deno 19.198 µs --- sha512-1000 --- ../../../deno/target/release-lite/deno 21.168 µs 1.026 times faster /home/nathanwhit/.deno/bin/deno 21.717 µs --- sha256-10000 --- ../../../deno/target/release-lite/deno 33.586 µs 1.990 times faster /home/nathanwhit/.deno/bin/deno 66.837 µs --- sha512-10000 --- ../../../deno/target/release-lite/deno 53.338 µs 1.009 times faster /home/nathanwhit/.deno/bin/deno 53.817 µs --- sha256-100000 --- ../../../deno/target/release-lite/deno 168.238 µs 3.063 times faster /home/nathanwhit/.deno/bin/deno 515.354 µs --- sha512-100000 --- ../../../deno/target/release-lite/deno 383.311 µs 1.036 times faster /home/nathanwhit/.deno/bin/deno 397.122 µs --- sha256-1000000 --- ../../../deno/target/release-lite/deno 1.474 ms 3.471 times faster /home/nathanwhit/.deno/bin/deno 5.115 ms --- sha512-1000000 --- ../../../deno/target/release-lite/deno 3.658 ms 1.057 times faster /home/nathanwhit/.deno/bin/deno 3.865 ms --- sha256-10000000 --- ../../../deno/target/release-lite/deno 16.438 ms 3.136 times faster /home/nathanwhit/.deno/bin/deno 51.556 ms --- sha512-10000000 --- ../../../deno/target/release-lite/deno 37.128 ms 1.056 times faster /home/nathanwhit/.deno/bin/deno 39.220 ms ``` </details>
346 lines
9.5 KiB
Rust
346 lines
9.5 KiB
Rust
// Copyright 2018-2025 the Deno authors. MIT license.
|
|
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
use deno_core::GarbageCollected;
|
|
use digest::Digest;
|
|
use digest::DynDigest;
|
|
use digest::ExtendableOutput;
|
|
use digest::Update;
|
|
|
|
mod ring_sha2;
|
|
|
|
pub struct Hasher {
|
|
pub hash: Rc<RefCell<Option<Hash>>>,
|
|
}
|
|
|
|
impl GarbageCollected for Hasher {}
|
|
|
|
impl Hasher {
|
|
pub fn new(
|
|
algorithm: &str,
|
|
output_length: Option<usize>,
|
|
) -> Result<Self, HashError> {
|
|
let hash = Hash::new(algorithm, output_length)?;
|
|
|
|
Ok(Self {
|
|
hash: Rc::new(RefCell::new(Some(hash))),
|
|
})
|
|
}
|
|
|
|
pub fn update(&self, data: &[u8]) -> bool {
|
|
if let Some(hash) = self.hash.borrow_mut().as_mut() {
|
|
hash.update(data);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn digest(&self) -> Option<Box<[u8]>> {
|
|
let hash = self.hash.borrow_mut().take()?;
|
|
Some(hash.digest_and_drop())
|
|
}
|
|
|
|
pub fn clone_inner(
|
|
&self,
|
|
output_length: Option<usize>,
|
|
) -> Result<Option<Self>, HashError> {
|
|
let hash = self.hash.borrow();
|
|
let Some(hash) = hash.as_ref() else {
|
|
return Ok(None);
|
|
};
|
|
let hash = hash.clone_hash(output_length)?;
|
|
Ok(Some(Self {
|
|
hash: Rc::new(RefCell::new(Some(hash))),
|
|
}))
|
|
}
|
|
}
|
|
|
|
macro_rules! match_fixed_digest {
|
|
($algorithm_name:expr, fn <$type:ident>() $body:block, _ => $other:block) => {
|
|
match $algorithm_name {
|
|
"blake2b512" => {
|
|
type $type = ::blake2::Blake2b512;
|
|
$body
|
|
}
|
|
"blake2s256" => {
|
|
type $type = ::blake2::Blake2s256;
|
|
$body
|
|
}
|
|
_ => crate::ops::crypto::digest::match_fixed_digest_with_eager_block_buffer!($algorithm_name, fn <$type>() $body, _ => $other)
|
|
}
|
|
};
|
|
}
|
|
pub(crate) use match_fixed_digest;
|
|
|
|
macro_rules! match_fixed_digest_with_eager_block_buffer {
|
|
($algorithm_name:expr, fn <$type:ident>() $body:block, _ => $other:block) => {
|
|
match $algorithm_name {
|
|
"rsa-sm3" | "sm3" | "sm3withrsaencryption" => {
|
|
type $type = ::sm3::Sm3;
|
|
$body
|
|
}
|
|
"rsa-md4" | "md4" | "md4withrsaencryption" => {
|
|
type $type = ::md4::Md4;
|
|
$body
|
|
}
|
|
"md5-sha1" => {
|
|
type $type = crate::ops::crypto::md5_sha1::Md5Sha1;
|
|
$body
|
|
}
|
|
_ => crate::ops::crypto::digest::match_fixed_digest_with_oid!($algorithm_name, fn <$type>() $body, _ => $other)
|
|
}
|
|
};
|
|
}
|
|
pub(crate) use match_fixed_digest_with_eager_block_buffer;
|
|
|
|
macro_rules! match_fixed_digest_with_oid {
|
|
($algorithm_name:expr, fn $(<$type:ident>)?($($hash_algorithm:ident: Option<RsaPssHashAlgorithm>)?) $body:block, _ => $other:block) => {
|
|
match $algorithm_name {
|
|
"rsa-md5" | "md5" | "md5withrsaencryption" | "ssl3-md5" => {
|
|
$(let $hash_algorithm = None;)?
|
|
$(type $type = ::md5::Md5;)?
|
|
$body
|
|
}
|
|
"rsa-ripemd160" | "ripemd" | "ripemd160" | "ripemd160withrsa"
|
|
| "rmd160" => {
|
|
$(let $hash_algorithm = None;)?
|
|
$(type $type = ::ripemd::Ripemd160;)?
|
|
$body
|
|
}
|
|
"rsa-sha1"
|
|
| "rsa-sha1-2"
|
|
| "sha1"
|
|
| "sha1-2"
|
|
| "sha1withrsaencryption"
|
|
| "ssl3-sha1" => {
|
|
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha1);)?
|
|
$(type $type = ::sha1::Sha1;)?
|
|
$body
|
|
}
|
|
"rsa-sha224" | "sha224" | "sha224withrsaencryption" => {
|
|
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha224);)?
|
|
$(type $type = ::sha2::Sha224;)?
|
|
$body
|
|
}
|
|
"rsa-sha256" | "sha256" | "sha256withrsaencryption" => {
|
|
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha256);)?
|
|
$(type $type = ::sha2::Sha256;)?
|
|
$body
|
|
}
|
|
"rsa-sha384" | "sha384" | "sha384withrsaencryption" => {
|
|
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha384);)?
|
|
$(type $type = ::sha2::Sha384;)?
|
|
$body
|
|
}
|
|
"rsa-sha512" | "sha512" | "sha512withrsaencryption" => {
|
|
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512);)?
|
|
$(type $type = ::sha2::Sha512;)?
|
|
$body
|
|
}
|
|
"rsa-sha512/224" | "sha512-224" | "sha512-224withrsaencryption" => {
|
|
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512_224);)?
|
|
$(type $type = ::sha2::Sha512_224;)?
|
|
$body
|
|
}
|
|
"rsa-sha512/256" | "sha512-256" | "sha512-256withrsaencryption" => {
|
|
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512_256);)?
|
|
$(type $type = ::sha2::Sha512_256;)?
|
|
$body
|
|
}
|
|
"rsa-sha3-224" | "id-rsassa-pkcs1-v1_5-with-sha3-224" | "sha3-224" => {
|
|
$(let $hash_algorithm = None;)?
|
|
$(type $type = ::sha3::Sha3_224;)?
|
|
$body
|
|
}
|
|
"rsa-sha3-256" | "id-rsassa-pkcs1-v1_5-with-sha3-256" | "sha3-256" => {
|
|
$(let $hash_algorithm = None;)?
|
|
$(type $type = ::sha3::Sha3_256;)?
|
|
$body
|
|
}
|
|
"rsa-sha3-384" | "id-rsassa-pkcs1-v1_5-with-sha3-384" | "sha3-384" => {
|
|
$(let $hash_algorithm = None;)?
|
|
$(type $type = ::sha3::Sha3_384;)?
|
|
$body
|
|
}
|
|
"rsa-sha3-512" | "id-rsassa-pkcs1-v1_5-with-sha3-512" | "sha3-512" => {
|
|
$(let $hash_algorithm = None;)?
|
|
$(type $type = ::sha3::Sha3_512;)?
|
|
$body
|
|
}
|
|
_ => $other,
|
|
}
|
|
};
|
|
}
|
|
|
|
pub(crate) use match_fixed_digest_with_oid;
|
|
|
|
pub enum Hash {
|
|
FixedSize(Box<dyn DynDigest>),
|
|
|
|
Shake128(Box<sha3::Shake128>, /* output_length: */ Option<usize>),
|
|
Shake256(Box<sha3::Shake256>, /* output_length: */ Option<usize>),
|
|
}
|
|
|
|
use Hash::*;
|
|
|
|
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
|
#[class(generic)]
|
|
pub enum HashError {
|
|
#[error("Output length mismatch for non-extendable algorithm")]
|
|
OutputLengthMismatch,
|
|
#[error("Digest method not supported: {0}")]
|
|
DigestMethodUnsupported(String),
|
|
}
|
|
|
|
impl Hash {
|
|
pub fn new(
|
|
algorithm_name: &str,
|
|
output_length: Option<usize>,
|
|
) -> Result<Self, HashError> {
|
|
match algorithm_name {
|
|
"shake128" => return Ok(Shake128(Default::default(), output_length)),
|
|
"shake256" => return Ok(Shake256(Default::default(), output_length)),
|
|
"sha256" => {
|
|
let digest = ring_sha2::RingSha256::new();
|
|
if let Some(length) = output_length {
|
|
if length != digest.output_size() {
|
|
return Err(HashError::OutputLengthMismatch);
|
|
}
|
|
}
|
|
return Ok(Hash::FixedSize(Box::new(digest)));
|
|
}
|
|
"sha512" => {
|
|
let digest = ring_sha2::RingSha512::new();
|
|
if let Some(length) = output_length {
|
|
if length != digest.output_size() {
|
|
return Err(HashError::OutputLengthMismatch);
|
|
}
|
|
}
|
|
return Ok(Hash::FixedSize(Box::new(digest)));
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let algorithm = match_fixed_digest!(
|
|
algorithm_name,
|
|
fn <D>() {
|
|
let digest: D = Digest::new();
|
|
if let Some(length) = output_length {
|
|
if length != digest.output_size() {
|
|
return Err(HashError::OutputLengthMismatch);
|
|
}
|
|
}
|
|
FixedSize(Box::new(digest))
|
|
},
|
|
_ => {
|
|
return Err(HashError::DigestMethodUnsupported(algorithm_name.to_string()))
|
|
}
|
|
);
|
|
|
|
Ok(algorithm)
|
|
}
|
|
|
|
pub fn update(&mut self, data: &[u8]) {
|
|
match self {
|
|
FixedSize(context) => DynDigest::update(&mut **context, data),
|
|
Shake128(context, _) => Update::update(&mut **context, data),
|
|
Shake256(context, _) => Update::update(&mut **context, data),
|
|
};
|
|
}
|
|
|
|
pub fn digest_and_drop(self) -> Box<[u8]> {
|
|
match self {
|
|
FixedSize(context) => context.finalize(),
|
|
|
|
// The default output lengths align with Node.js
|
|
Shake128(context, output_length) => {
|
|
context.finalize_boxed(output_length.unwrap_or(16))
|
|
}
|
|
Shake256(context, output_length) => {
|
|
context.finalize_boxed(output_length.unwrap_or(32))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn clone_hash(
|
|
&self,
|
|
output_length: Option<usize>,
|
|
) -> Result<Self, HashError> {
|
|
let hash = match self {
|
|
FixedSize(context) => {
|
|
if let Some(length) = output_length {
|
|
if length != context.output_size() {
|
|
return Err(HashError::OutputLengthMismatch);
|
|
}
|
|
}
|
|
FixedSize(context.box_clone())
|
|
}
|
|
|
|
Shake128(context, _) => Shake128(context.clone(), output_length),
|
|
Shake256(context, _) => Shake256(context.clone(), output_length),
|
|
};
|
|
Ok(hash)
|
|
}
|
|
|
|
pub fn get_hashes() -> Vec<&'static str> {
|
|
vec![
|
|
"RSA-MD4",
|
|
"RSA-MD5",
|
|
"RSA-RIPEMD160",
|
|
"RSA-SHA1",
|
|
"RSA-SHA1-2",
|
|
"RSA-SHA224",
|
|
"RSA-SHA256",
|
|
"RSA-SHA3-224",
|
|
"RSA-SHA3-256",
|
|
"RSA-SHA3-384",
|
|
"RSA-SHA3-512",
|
|
"RSA-SHA384",
|
|
"RSA-SHA512",
|
|
"RSA-SHA512/224",
|
|
"RSA-SHA512/256",
|
|
"RSA-SM3",
|
|
"blake2b512",
|
|
"blake2s256",
|
|
"id-rsassa-pkcs1-v1_5-with-sha3-224",
|
|
"id-rsassa-pkcs1-v1_5-with-sha3-256",
|
|
"id-rsassa-pkcs1-v1_5-with-sha3-384",
|
|
"id-rsassa-pkcs1-v1_5-with-sha3-512",
|
|
"md4",
|
|
"md4WithRSAEncryption",
|
|
"md5",
|
|
"md5-sha1",
|
|
"md5WithRSAEncryption",
|
|
"ripemd",
|
|
"ripemd160",
|
|
"ripemd160WithRSA",
|
|
"rmd160",
|
|
"sha1",
|
|
"sha1WithRSAEncryption",
|
|
"sha224",
|
|
"sha224WithRSAEncryption",
|
|
"sha256",
|
|
"sha256WithRSAEncryption",
|
|
"sha3-224",
|
|
"sha3-256",
|
|
"sha3-384",
|
|
"sha3-512",
|
|
"sha384",
|
|
"sha384WithRSAEncryption",
|
|
"sha512",
|
|
"sha512-224",
|
|
"sha512-224WithRSAEncryption",
|
|
"sha512-256",
|
|
"sha512-256WithRSAEncryption",
|
|
"sha512WithRSAEncryption",
|
|
"shake128",
|
|
"shake256",
|
|
"sm3",
|
|
"sm3WithRSAEncryption",
|
|
"ssl3-md5",
|
|
"ssl3-sha1",
|
|
]
|
|
}
|
|
}
|