0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-24 14:11:58 -05:00
denoland-deno/ext/node/ops/crypto/digest.rs

347 lines
9.5 KiB
Rust
Raw Normal View History

2025-01-01 04:12:39 +09:00
// 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;
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 15:38:14 -08:00
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)),
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 15:38:14 -08:00
"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",
]
}
}