0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-23 05:33:33 -05:00
denoland-deno/ext/node/ops/crypto/digest.rs
Nathan Whitaker bac8171c40
perf(crypto): use ring for asm implementations of sha256/sha512 (#27885)
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>
2025-01-30 23:38:14 +00:00

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",
]
}
}