1use crate::error::{Error, Result};
12use base64::{Engine as _, engine::general_purpose};
13use hmac::{Hmac, Mac};
14use sha1::Sha1;
21use sha2::{Digest, Sha256, Sha384, Sha512};
22use sha3::Keccak256;
23use std::fmt;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum HashAlgorithm {
28 Sha1,
30 Sha256,
32 Sha384,
34 Sha512,
36 Md5,
38 Keccak,
40}
41
42impl HashAlgorithm {
43 pub fn from_str(s: &str) -> Result<Self> {
51 match s.to_lowercase().as_str() {
52 "sha1" => Ok(HashAlgorithm::Sha1),
53 "sha256" => Ok(HashAlgorithm::Sha256),
54 "sha384" => Ok(HashAlgorithm::Sha384),
55 "sha512" => Ok(HashAlgorithm::Sha512),
56 "md5" => Ok(HashAlgorithm::Md5),
57 "keccak" | "sha3" => Ok(HashAlgorithm::Keccak),
58 _ => Err(Error::invalid_argument(format!(
59 "Unsupported hash algorithm: {}",
60 s
61 ))),
62 }
63 }
64}
65
66impl fmt::Display for HashAlgorithm {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 let s = match self {
69 HashAlgorithm::Sha1 => "sha1",
70 HashAlgorithm::Sha256 => "sha256",
71 HashAlgorithm::Sha384 => "sha384",
72 HashAlgorithm::Sha512 => "sha512",
73 HashAlgorithm::Md5 => "md5",
74 HashAlgorithm::Keccak => "keccak",
75 };
76 write!(f, "{}", s)
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum DigestFormat {
83 Hex,
85 Base64,
87 Binary,
89}
90
91impl DigestFormat {
92 pub fn from_str(s: &str) -> Self {
100 match s.to_lowercase().as_str() {
101 "hex" => DigestFormat::Hex,
102 "base64" => DigestFormat::Base64,
103 "binary" => DigestFormat::Binary,
104 _ => DigestFormat::Hex,
105 }
106 }
107}
108
109pub fn hmac_sign(
135 message: &str,
136 secret: &str,
137 algorithm: HashAlgorithm,
138 digest: DigestFormat,
139) -> Result<String> {
140 let signature = match algorithm {
141 HashAlgorithm::Sha256 => hmac_sha256(message.as_bytes(), secret.as_bytes()),
142 HashAlgorithm::Sha512 => hmac_sha512(message.as_bytes(), secret.as_bytes()),
143 HashAlgorithm::Sha384 => hmac_sha384(message.as_bytes(), secret.as_bytes()),
144 HashAlgorithm::Md5 => hmac_md5(message.as_bytes(), secret.as_bytes()),
145 _ => {
146 return Err(Error::invalid_argument(format!(
147 "HMAC does not support {} algorithm",
148 algorithm
149 )));
150 }
151 };
152
153 Ok(encode_bytes(&signature, digest))
154}
155
156fn hmac_sha256(data: &[u8], secret: &[u8]) -> Vec<u8> {
165 type HmacSha256 = Hmac<Sha256>;
166 let mut mac = HmacSha256::new_from_slice(secret)
168 .expect("HMAC-SHA256 accepts keys of any length; this is an infallible operation");
169 mac.update(data);
170 mac.finalize().into_bytes().to_vec()
171}
172
173fn hmac_sha512(data: &[u8], secret: &[u8]) -> Vec<u8> {
179 type HmacSha512 = Hmac<Sha512>;
180 let mut mac = HmacSha512::new_from_slice(secret)
182 .expect("HMAC-SHA512 accepts keys of any length; this is an infallible operation");
183 mac.update(data);
184 mac.finalize().into_bytes().to_vec()
185}
186
187fn hmac_sha384(data: &[u8], secret: &[u8]) -> Vec<u8> {
193 type HmacSha384 = Hmac<Sha384>;
194 let mut mac = HmacSha384::new_from_slice(secret)
196 .expect("HMAC-SHA384 accepts keys of any length; this is an infallible operation");
197 mac.update(data);
198 mac.finalize().into_bytes().to_vec()
199}
200
201fn hmac_md5(data: &[u8], secret: &[u8]) -> Vec<u8> {
207 use md5::Md5;
208 type HmacMd5 = Hmac<Md5>;
209 let mut mac = HmacMd5::new_from_slice(secret)
211 .expect("HMAC-MD5 accepts keys of any length; this is an infallible operation");
212 mac.update(data);
213 mac.finalize().into_bytes().to_vec()
214}
215
216pub fn hash(data: &str, algorithm: HashAlgorithm, digest: DigestFormat) -> Result<String> {
233 let hash_bytes = match algorithm {
234 HashAlgorithm::Sha256 => hash_sha256(data.as_bytes()),
235 HashAlgorithm::Sha512 => hash_sha512(data.as_bytes()),
236 HashAlgorithm::Sha384 => hash_sha384(data.as_bytes()),
237 HashAlgorithm::Sha1 => hash_sha1(data.as_bytes()),
238 HashAlgorithm::Md5 => hash_md5(data.as_bytes()),
239 HashAlgorithm::Keccak => hash_keccak(data.as_bytes()),
240 };
241
242 Ok(encode_bytes(&hash_bytes, digest))
243}
244
245fn hash_sha256(data: &[u8]) -> Vec<u8> {
247 let mut hasher = Sha256::new();
248 hasher.update(data);
249 hasher.finalize().to_vec()
250}
251
252fn hash_sha512(data: &[u8]) -> Vec<u8> {
254 let mut hasher = Sha512::new();
255 hasher.update(data);
256 hasher.finalize().to_vec()
257}
258
259fn hash_sha384(data: &[u8]) -> Vec<u8> {
261 let mut hasher = Sha384::new();
262 hasher.update(data);
263 hasher.finalize().to_vec()
264}
265
266fn hash_sha1(data: &[u8]) -> Vec<u8> {
268 let mut hasher = Sha1::new();
269 hasher.update(data);
270 hasher.finalize().to_vec()
271}
272
273fn hash_md5(data: &[u8]) -> Vec<u8> {
275 use md5::{Digest, Md5};
276 let mut hasher = Md5::new();
277 hasher.update(data);
278 hasher.finalize().to_vec()
279}
280
281fn hash_keccak(data: &[u8]) -> Vec<u8> {
283 let mut hasher = Keccak256::new();
284 hasher.update(data);
285 hasher.finalize().to_vec()
286}
287
288pub fn eddsa_sign(data: &str, secret_key: &[u8]) -> Result<String> {
357 use ed25519_dalek::{Signature, Signer, SigningKey};
358
359 if secret_key.len() != 32 {
360 return Err(Error::invalid_argument(format!(
361 "Ed25519 secret key must be 32 bytes, got {}",
362 secret_key.len()
363 )));
364 }
365
366 let signing_key = SigningKey::from_bytes(
367 secret_key
368 .try_into()
369 .map_err(|_| Error::invalid_argument("Invalid Ed25519 key".to_string()))?,
370 );
371
372 let signature: Signature = signing_key.sign(data.as_bytes());
373 let encoded = general_purpose::STANDARD.encode(signature.to_bytes());
374
375 Ok(base64_to_base64url(&encoded, true))
376}
377
378pub fn jwt_sign(
405 payload: &serde_json::Value,
406 secret: &str,
407 algorithm: HashAlgorithm,
408 header_options: Option<serde_json::Map<String, serde_json::Value>>,
409) -> Result<String> {
410 let mut header = serde_json::Map::new();
411 header.insert(
412 "alg".to_string(),
413 serde_json::Value::String("HS256".to_string()),
414 );
415 header.insert(
416 "typ".to_string(),
417 serde_json::Value::String("JWT".to_string()),
418 );
419
420 if let Some(options) = header_options {
421 for (key, value) in options {
422 header.insert(key, value);
423 }
424 }
425
426 let header_json = serde_json::to_string(&header)?;
427 let payload_json = serde_json::to_string(payload)?;
428
429 let encoded_header = general_purpose::URL_SAFE_NO_PAD.encode(header_json.as_bytes());
430 let encoded_payload = general_purpose::URL_SAFE_NO_PAD.encode(payload_json.as_bytes());
431
432 let token = format!("{}.{}", encoded_header, encoded_payload);
433
434 let signature = hmac_sign(&token, secret, algorithm, DigestFormat::Base64)?;
435
436 let signature_url = base64_to_base64url(&signature, true);
437
438 Ok(format!("{}.{}", token, signature_url))
439}
440
441fn encode_bytes(bytes: &[u8], format: DigestFormat) -> String {
443 match format {
444 DigestFormat::Hex => hex::encode(bytes),
445 DigestFormat::Base64 => general_purpose::STANDARD.encode(bytes),
446 DigestFormat::Binary => String::from_utf8_lossy(bytes).to_string(),
447 }
448}
449
450pub fn base64_to_base64url(base64_str: &str, strip_padding: bool) -> String {
459 let mut result = base64_str.replace('+', "-").replace('/', "_");
460 if strip_padding {
461 result = result.trim_end_matches('=').to_string();
462 }
463 result
464}
465
466pub fn base64url_decode(base64url: &str) -> Result<Vec<u8>> {
477 let base64 = base64url.replace('-', "+").replace('_', "/");
478
479 let padding = match base64.len() % 4 {
480 2 => "==",
481 3 => "=",
482 _ => "",
483 };
484 let base64_padded = format!("{}{}", base64, padding);
485
486 general_purpose::STANDARD
487 .decode(base64_padded.as_bytes())
488 .map_err(|e| Error::invalid_argument(format!("Base64 decode error: {}", e)))
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 #[test]
496 fn test_hmac_sha256_hex() {
497 let result = hmac_sign("test", "secret", HashAlgorithm::Sha256, DigestFormat::Hex).unwrap();
498 assert_eq!(
499 result,
500 "0329a06b62cd16b33eb6792be8c60b158d89a2ee3a876fce9a881ebb488c0914"
501 );
502 }
503
504 #[test]
505 fn test_hmac_sha256_base64() {
506 let result = hmac_sign(
507 "test",
508 "secret",
509 HashAlgorithm::Sha256,
510 DigestFormat::Base64,
511 )
512 .unwrap();
513 assert!(!result.is_empty());
514 }
515
516 #[test]
517 fn test_hash_sha256() {
518 let result = hash("test", HashAlgorithm::Sha256, DigestFormat::Hex).unwrap();
519 assert_eq!(
520 result,
521 "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
522 );
523 }
524
525 #[test]
526 fn test_hash_keccak() {
527 let result = hash("test", HashAlgorithm::Keccak, DigestFormat::Hex).unwrap();
528 assert_eq!(result.len(), 64); }
530
531 #[test]
532 fn test_base64_to_base64url() {
533 let base64 = "abc+def/ghi==";
534 let base64url = base64_to_base64url(base64, true);
535 assert_eq!(base64url, "abc-def_ghi");
536 }
537
538 #[test]
539 fn test_base64url_decode() {
540 let base64url = "abc-def_ghg";
541 let decoded = base64url_decode(base64url).unwrap();
542 assert!(!decoded.is_empty());
543 }
544
545 #[test]
546 fn test_jwt_sign() {
547 use serde_json::json;
548
549 let payload = json!({
550 "user_id": "123",
551 "exp": 1234567890
552 });
553
554 let token = jwt_sign(&payload, "secret", HashAlgorithm::Sha256, None).unwrap();
555
556 let parts: Vec<&str> = token.split('.').collect();
558 assert_eq!(parts.len(), 3);
559 }
560
561 #[test]
562 fn test_hash_algorithm_from_str() {
563 assert_eq!(
564 HashAlgorithm::from_str("sha256").unwrap(),
565 HashAlgorithm::Sha256
566 );
567 assert_eq!(
568 HashAlgorithm::from_str("SHA256").unwrap(),
569 HashAlgorithm::Sha256
570 );
571 assert!(HashAlgorithm::from_str("invalid").is_err());
572 }
573
574 #[test]
575 fn test_digest_format_from_str() {
576 assert_eq!(DigestFormat::from_str("hex"), DigestFormat::Hex);
577 assert_eq!(DigestFormat::from_str("base64"), DigestFormat::Base64);
578 assert_eq!(DigestFormat::from_str("binary"), DigestFormat::Binary);
579 assert_eq!(DigestFormat::from_str("unknown"), DigestFormat::Hex); }
581
582 #[test]
583 fn test_hmac_sha512() {
584 let result = hmac_sign("test", "secret", HashAlgorithm::Sha512, DigestFormat::Hex).unwrap();
585 assert_eq!(result.len(), 128); }
587
588 #[test]
589 fn test_hash_md5() {
590 let result = hash("test", HashAlgorithm::Md5, DigestFormat::Hex).unwrap();
591 assert_eq!(result.len(), 32); }
593}