공개된 소스
소스 ZIP 다운로드
CH-J Tools
특정 애플리케이션 릴리스의 디렉터리와 파일을 살펴보세요.
CH-J / Server Manager
872 개 파일
core-20261030T220507Z-61e4558d889f
추가됨: · 빌드됨:
1
//! Supported local hash algorithms (port of `hashing/hashAlgorithms.js`, same ids and order).3
use crate::core::error::{CoreError, CoreResult};4
use serde_json::{json, Map, Value};6
#[derive(Debug, Clone, Copy)]7
pub struct Xof {8
pub default_bytes: usize,9
pub min_bytes: usize,10
pub max_bytes: usize,11
}13
#[derive(Debug, Clone, Copy)]14
pub struct Algorithm {15
pub id: &'static str,16
pub name: &'static str,17
pub category: &'static str,18
pub digest_bits: Option<u32>,19
pub xof: Option<Xof>,20
pub seed_bits: Option<u32>,21
pub key_bytes: Option<usize>,22
pub random_access: bool,23
pub parameters: Option<&'static str>,24
}26
const fn alg(id: &'static str, name: &'static str, category: &'static str, bits: u32) -> Algorithm {27
Algorithm { id, name, category, digest_bits: Some(bits), xof: None, seed_bits: None, key_bytes: None, random_access: false, parameters: None }28
}29
const fn xof(id: &'static str, name: &'static str, default_bytes: usize) -> Algorithm {30
Algorithm { digest_bits: None, xof: Some(Xof { default_bytes, min_bytes: 16, max_bytes: 1024 }), ..alg(id, name, "xof", 0) }31
}32
const fn seeded(id: &'static str, name: &'static str, bits: u32, seed_bits: u32) -> Algorithm {33
Algorithm { seed_bits: Some(seed_bits), ..alg(id, name, "fast", bits) }34
}35
const fn random(id: &'static str, name: &'static str, bits: u32) -> Algorithm {36
Algorithm { random_access: true, ..alg(id, name, "fast", bits) }37
}38
const fn keyed(id: &'static str, name: &'static str, bits: u32, key_bytes: usize) -> Algorithm {39
Algorithm { key_bytes: Some(key_bytes), ..alg(id, name, "fast", bits) }40
}41
const fn crc(id: &'static str, name: &'static str, bits: u32, parameters: &'static str) -> Algorithm {42
Algorithm { parameters: Some(parameters), ..alg(id, name, "checksums", bits) }43
}45
pub const ALGORITHMS: [Algorithm; 49] = [46
alg("sha224", "SHA-224", "legacy", 224),47
alg("sha256", "SHA-256", "recommended", 256),48
alg("sha384", "SHA-384", "recommended", 384),49
alg("sha512", "SHA-512", "recommended", 512),50
alg("sha512-224", "SHA-512/224", "recommended", 224),51
alg("sha512-256", "SHA-512/256", "recommended", 256),52
alg("sha3-224", "SHA3-224", "recommended", 224),53
alg("sha3-256", "SHA3-256", "recommended", 256),54
alg("sha3-384", "SHA3-384", "recommended", 384),55
alg("sha3-512", "SHA3-512", "recommended", 512),56
xof("shake128", "SHAKE128", 32),57
xof("shake256", "SHAKE256", 64),58
alg("blake2b-512", "BLAKE2b-512", "recommended", 512),59
alg("blake2s-256", "BLAKE2s-256", "recommended", 256),60
alg("blake3", "BLAKE3", "recommended", 256),61
xof("kangaroo-twelve", "KangarooTwelve", 32),62
alg("ripemd160", "RIPEMD-160", "legacy", 160),63
alg("whirlpool", "Whirlpool", "legacy", 512),64
alg("tiger", "Tiger", "legacy", 192),65
alg("tiger2", "Tiger2", "legacy", 192),66
alg("md5", "MD5", "broken", 128),67
alg("sha1", "SHA-1", "broken", 160),68
seeded("xxh32", "XXH32", 32, 32),69
seeded("xxh64", "XXH64", 64, 64),70
seeded("xxh3-64", "XXH3-64", 64, 64),71
seeded("xxh3-128", "XXH3-128", 128, 64),72
seeded("murmur3-x86-32", "MurmurHash3 x86 32", 32, 32),73
seeded("murmur3-x86-128", "MurmurHash3 x86 128", 128, 32),74
seeded("murmur3-x64-128", "MurmurHash3 x64 128", 128, 32),75
random("cityhash32", "CityHash32", 32),76
random("cityhash64", "CityHash64", 64),77
random("cityhash128", "CityHash128", 128),78
random("farmhash32", "FarmHash32", 32),79
random("farmhash64", "FarmHash64", 64),80
random("farmhash128", "FarmHash128", 128),81
keyed("highwayhash64", "HighwayHash64", 64, 32),82
keyed("highwayhash128", "HighwayHash128", 128, 32),83
keyed("highwayhash256", "HighwayHash256", 256, 32),84
keyed("siphash-2-4", "SipHash-2-4", 64, 16),85
alg("fnv1-32", "FNV-1 32", "fast", 32),86
alg("fnv1-64", "FNV-1 64", "fast", 64),87
alg("fnv1a-32", "FNV-1a 32", "fast", 32),88
alg("fnv1a-64", "FNV-1a 64", "fast", 64),89
crc("crc16-ccitt-false", "CRC-16/CCITT-FALSE", 16, "poly=0x1021 init=0xffff refin=false refout=false xorout=0x0000"),90
crc("crc32", "CRC-32/ISO-HDLC", 32, "poly=0x04c11db7 init=0xffffffff refin=true refout=true xorout=0xffffffff"),91
crc("crc32c", "CRC-32C/Castagnoli", 32, "poly=0x1edc6f41 init=0xffffffff refin=true refout=true xorout=0xffffffff"),92
crc("crc64-ecma", "CRC-64/ECMA-182", 64, "poly=0x42f0e1eba9ea3693 init=0 refin=false refout=false xorout=0"),93
crc("crc64-xz", "CRC-64/XZ", 64, "poly=0x42f0e1eba9ea3693 init=all-ones refin=true refout=true xorout=all-ones"),94
alg("adler32", "Adler-32", "checksums", 32),95
];97
pub fn by_id(id: &str) -> Option<&'static Algorithm> {98
ALGORITHMS.iter().find(|a| a.id == id)99
}101
fn category_name(category: &str) -> &'static str {102
match category {103
"recommended" => "Recommended / Cryptographic",104
"xof" => "XOF",105
"legacy" => "Legacy Cryptographic",106
"broken" => "Broken / Compatibility only",107
"fast" => "Fast non-cryptographic",108
_ => "Checksums",109
}110
}112
pub fn typed(code: &str, message: impl Into<String>) -> CoreError {113
CoreError::new(code, message)114
}116
impl Algorithm {117
pub fn public(&self) -> Value {118
let mut map = Map::new();119
map.insert("id".into(), json!(self.id));120
map.insert("name".into(), json!(self.name));121
map.insert("category".into(), json!(self.category));122
map.insert("categoryName".into(), json!(category_name(self.category)));123
map.insert("digestBits".into(), json!(self.digest_bits));124
map.insert("backend".into(), json!("rust-core"));125
map.insert("cryptographic".into(), json!(matches!(self.category, "recommended" | "xof" | "legacy" | "broken")));126
map.insert(127
"securityStatus".into(),128
json!(match self.category {129
"broken" => "broken",130
"fast" | "checksums" => "non-cryptographic",131
_ => "cryptographic",132
}),133
);134
map.insert("supportsStreaming".into(), json!(!self.random_access));135
if let Some(x) = self.xof {136
map.insert("xof".into(), json!({ "defaultBytes": x.default_bytes, "minBytes": x.min_bytes, "maxBytes": x.max_bytes }));137
}138
if let Some(bits) = self.seed_bits {139
map.insert("seeded".into(), json!(true));140
map.insert("seedBits".into(), json!(bits));141
}142
if let Some(bytes) = self.key_bytes {143
map.insert("keyed".into(), json!(true));144
map.insert("keyBytes".into(), json!(bytes));145
}146
if self.random_access {147
map.insert("randomAccess".into(), json!(true));148
}149
if let Some(p) = self.parameters {150
map.insert("parameters".into(), json!(p));151
}152
Value::Object(map)153
}155
pub fn output_bytes(&self, request: &Request) -> usize {156
request.output_bytes.unwrap_or_else(|| self.digest_bits.unwrap_or(0) as usize / 8)157
}158
}160
pub fn public_list() -> Value {161
Value::Array(ALGORITHMS.iter().map(Algorithm::public).collect())162
}164
/// Normalized algorithm request (`{id, outputBytes?, seed?, keyHex?, defaultKey?}`).165
#[derive(Debug, Clone, PartialEq)]166
pub struct Request {167
pub id: &'static str,168
pub output_bytes: Option<usize>,169
pub seed: Option<u64>,170
pub key: Option<Vec<u8>>,171
pub default_key: bool,172
}174
impl Request {175
pub fn algorithm(&self) -> &'static Algorithm {176
by_id(self.id).expect("normalized request")177
}179
pub fn public(&self) -> Value {180
let mut map = Map::new();181
map.insert("id".into(), json!(self.id));182
if let Some(bytes) = self.output_bytes {183
map.insert("outputBytes".into(), json!(bytes));184
}185
if let Some(seed) = self.seed {186
map.insert("seed".into(), json!(seed.to_string()));187
}188
if let Some(key) = &self.key {189
map.insert("keyHex".into(), json!(hex::encode(key)));190
map.insert("defaultKey".into(), json!(self.default_key));191
}192
Value::Object(map)193
}195
pub fn simple(id: &'static str) -> Self {196
Request { id, output_bytes: None, seed: None, key: None, default_key: false }197
}198
}200
fn normalize_seed(value: Option<&Value>, bits: u32) -> CoreResult<u64> {201
let source = match value {202
None | Some(Value::Null) => return Ok(0),203
Some(Value::String(s)) => s.trim().to_string(),204
Some(Value::Number(n)) => n.to_string(),205
Some(other) => other.to_string(),206
};207
if source.is_empty() {208
return Ok(0);209
}210
let invalid = || typed("HASH_INVALID_SEED", "Seed must be an unsigned decimal or 0x-prefixed hexadecimal integer.");211
let parsed: u128 = if let Some(hex) = source.strip_prefix("0x").or_else(|| source.strip_prefix("0X")) {212
if hex.is_empty() || !hex.chars().all(|c| c.is_ascii_hexdigit()) {213
return Err(invalid());214
}215
let trimmed = hex.trim_start_matches('0');216
if trimmed.len() > 32 {217
return Err(typed("HASH_INVALID_SEED", format!("Seed must fit in {bits} bits.")));218
}219
u128::from_str_radix(if trimmed.is_empty() { "0" } else { trimmed }, 16).map_err(|_| invalid())?220
} else {221
if !source.chars().all(|c| c.is_ascii_digit()) {222
return Err(invalid());223
}224
let trimmed = source.trim_start_matches('0');225
if trimmed.len() > 38 {226
return Err(typed("HASH_INVALID_SEED", format!("Seed must fit in {bits} bits.")));227
}228
if trimmed.is_empty() { 0 } else { trimmed.parse().map_err(|_| invalid())? }229
};230
if parsed >= (1u128 << bits) {231
return Err(typed("HASH_INVALID_SEED", format!("Seed must fit in {bits} bits.")));232
}233
Ok(parsed as u64)234
}236
pub fn normalize_requests(input: &Value) -> CoreResult<Vec<Request>> {237
let list = input.as_array().filter(|l| !l.is_empty() && l.len() <= ALGORITHMS.len()).ok_or_else(|| typed("HASH_INVALID_ALGORITHMS", "Select between one and all supported algorithms."))?;238
let mut seen: Vec<&str> = Vec::new();239
let mut out = Vec::new();240
for source in list {241
let id = match source {242
Value::String(s) => s.as_str(),243
Value::Object(o) => o.get("id").and_then(Value::as_str).unwrap_or(""),244
_ => "",245
};246
let algorithm = by_id(id).filter(|a| !seen.contains(&a.id)).ok_or_else(|| typed("HASH_UNSUPPORTED_ALGORITHM", format!("Unsupported or duplicate algorithm: {}", if id.is_empty() { "<empty>" } else { id })))?;247
seen.push(algorithm.id);248
let mut request = Request::simple(algorithm.id);249
if let Some(x) = algorithm.xof {250
let raw = source.get("outputBytes").filter(|v| !v.is_null());251
let value = match raw {252
None => Some(x.default_bytes as f64),253
Some(Value::Number(n)) => n.as_f64(),254
Some(Value::String(s)) => if s.trim().is_empty() { Some(0.0) } else { s.trim().parse::<f64>().ok() },255
Some(Value::Bool(b)) => Some(if *b { 1.0 } else { 0.0 }),256
_ => None,257
};258
match value {259
Some(v) if v.fract() == 0.0 && v >= x.min_bytes as f64 && v <= x.max_bytes as f64 => request.output_bytes = Some(v as usize),260
_ => return Err(typed("HASH_INVALID_XOF_LENGTH", format!("{} output must be {}-{} bytes.", algorithm.name, x.min_bytes, x.max_bytes))),261
}262
}263
if let Some(bits) = algorithm.seed_bits {264
request.seed = Some(normalize_seed(source.get("seed"), bits)?);265
}266
if let Some(bytes) = algorithm.key_bytes {267
let provided = source.get("keyHex").and_then(|v| match v {268
Value::String(s) if !s.is_empty() => Some(s.trim().to_lowercase()),269
Value::Null | Value::Bool(false) => None,270
Value::String(_) => None,271
other => Some(other.to_string()),272
});273
let key_hex = provided.clone().unwrap_or_else(|| "00".repeat(bytes));274
if key_hex.len() != bytes * 2 || !key_hex.chars().all(|c| c.is_ascii_digit() || ('a'..='f').contains(&c)) {275
return Err(typed("HASH_INVALID_KEY", format!("{} key must be exactly {bytes} bytes in hexadecimal.", algorithm.name)));276
}277
request.key = Some(hex::decode(&key_hex).map_err(|_| typed("HASH_INVALID_KEY", "Invalid key."))?);278
request.default_key = provided.is_none();279
}280
out.push(request);281
}282
Ok(out)283
}285
#[cfg(test)]286
mod tests {287
use super::*;289
#[test]290
fn normalizes_like_0_1_0() {291
assert_eq!(ALGORITHMS.len(), 49);292
let r = normalize_requests(&json!(["sha256", {"id": "shake128"}, {"id": "xxh64", "seed": "0x10"}, {"id": "siphash-2-4"}])).unwrap();293
assert_eq!(r[1].output_bytes, Some(32));294
assert_eq!(r[2].seed, Some(16));295
assert_eq!(r[3].key.as_deref(), Some(&[0u8; 16][..]));296
assert!(r[3].default_key);297
assert_eq!(normalize_requests(&json!(["sha256", "sha256"])).unwrap_err().code, "HASH_UNSUPPORTED_ALGORITHM");298
assert_eq!(normalize_requests(&json!([])).unwrap_err().code, "HASH_INVALID_ALGORITHMS");299
assert_eq!(normalize_requests(&json!([{"id": "xxh32", "seed": "4294967296"}])).unwrap_err().code, "HASH_INVALID_SEED");300
assert_eq!(normalize_requests(&json!([{"id": "xxh32", "seed": "-1"}])).unwrap_err().code, "HASH_INVALID_SEED");301
assert_eq!(normalize_requests(&json!([{"id": "shake256", "outputBytes": 8}])).unwrap_err().code, "HASH_INVALID_XOF_LENGTH");302
assert_eq!(normalize_requests(&json!([{"id": "highwayhash64", "keyHex": "zz"}])).unwrap_err().code, "HASH_INVALID_KEY");303
let list = public_list();304
assert_eq!(list[0]["categoryName"], "Legacy Cryptographic");305
assert_eq!(list[10]["xof"]["defaultBytes"], 32);306
assert!(list[0].get("xof").is_none());307
}308
}SHA-256
30751015ff3a553e56f3c93c33176a4bc895fa8a7fa489468f22a5737beab929CH-J Proprietary Software License 1.14 · 소스는 CH-J Proprietary Software License 1.14에 따라 제공됩니다. 소스를 공개한다고 해서 라이선스 조항이 바뀌거나 추가 권리가 부여되지는 않습니다.
압축 파일 SHA-256
77380d4805485ffa4027fbe7858b5836f197a4aa9d52358a43e2d12d018744df