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 #[allow(clippy::should_implement_trait)]
53 pub fn from_str(s: &str) -> Result<Self> {
54 match s.to_lowercase().as_str() {
55 "sha1" => Ok(HashAlgorithm::Sha1),
56 "sha256" => Ok(HashAlgorithm::Sha256),
57 "sha384" => Ok(HashAlgorithm::Sha384),
58 "sha512" => Ok(HashAlgorithm::Sha512),
59 "md5" => Ok(HashAlgorithm::Md5),
60 "keccak" | "sha3" => Ok(HashAlgorithm::Keccak),
61 _ => Err(Error::invalid_argument(format!(
62 "Unsupported hash algorithm: {s}"
63 ))),
64 }
65 }
66}
67
68impl fmt::Display for HashAlgorithm {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 let s = match self {
71 HashAlgorithm::Sha1 => "sha1",
72 HashAlgorithm::Sha256 => "sha256",
73 HashAlgorithm::Sha384 => "sha384",
74 HashAlgorithm::Sha512 => "sha512",
75 HashAlgorithm::Md5 => "md5",
76 HashAlgorithm::Keccak => "keccak",
77 };
78 write!(f, "{s}")
79 }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum DigestFormat {
85 Hex,
87 Base64,
89 Binary,
91}
92
93impl DigestFormat {
94 #[allow(clippy::should_implement_trait)]
104 #[allow(clippy::match_same_arms)]
107 pub fn from_str(s: &str) -> Self {
108 match s.to_lowercase().as_str() {
109 "hex" => DigestFormat::Hex,
110 "base64" => DigestFormat::Base64,
111 "binary" => DigestFormat::Binary,
112 _ => DigestFormat::Hex,
113 }
114 }
115}
116
117pub fn hmac_sign(
143 message: &str,
144 secret: &str,
145 algorithm: HashAlgorithm,
146 digest: DigestFormat,
147) -> Result<String> {
148 let signature = match algorithm {
149 HashAlgorithm::Sha256 => hmac_sha256(message.as_bytes(), secret.as_bytes()),
150 HashAlgorithm::Sha512 => hmac_sha512(message.as_bytes(), secret.as_bytes()),
151 HashAlgorithm::Sha384 => hmac_sha384(message.as_bytes(), secret.as_bytes()),
152 HashAlgorithm::Md5 => hmac_md5(message.as_bytes(), secret.as_bytes()),
153 _ => {
154 return Err(Error::invalid_argument(format!(
155 "HMAC does not support {algorithm} algorithm"
156 )));
157 }
158 };
159
160 Ok(encode_bytes(&signature, digest))
161}
162
163fn hmac_sha256(data: &[u8], secret: &[u8]) -> Vec<u8> {
172 type HmacSha256 = Hmac<Sha256>;
173 let mut mac = HmacSha256::new_from_slice(secret)
175 .expect("HMAC-SHA256 accepts keys of any length; this is an infallible operation");
176 mac.update(data);
177 mac.finalize().into_bytes().to_vec()
178}
179
180fn hmac_sha512(data: &[u8], secret: &[u8]) -> Vec<u8> {
186 type HmacSha512 = Hmac<Sha512>;
187 let mut mac = HmacSha512::new_from_slice(secret)
189 .expect("HMAC-SHA512 accepts keys of any length; this is an infallible operation");
190 mac.update(data);
191 mac.finalize().into_bytes().to_vec()
192}
193
194fn hmac_sha384(data: &[u8], secret: &[u8]) -> Vec<u8> {
200 type HmacSha384 = Hmac<Sha384>;
201 let mut mac = HmacSha384::new_from_slice(secret)
203 .expect("HMAC-SHA384 accepts keys of any length; this is an infallible operation");
204 mac.update(data);
205 mac.finalize().into_bytes().to_vec()
206}
207
208fn hmac_md5(data: &[u8], secret: &[u8]) -> Vec<u8> {
214 use md5::Md5;
215 type HmacMd5 = Hmac<Md5>;
216 let mut mac = HmacMd5::new_from_slice(secret)
218 .expect("HMAC-MD5 accepts keys of any length; this is an infallible operation");
219 mac.update(data);
220 mac.finalize().into_bytes().to_vec()
221}
222
223pub fn hash(data: &str, algorithm: HashAlgorithm, digest: DigestFormat) -> Result<String> {
240 let hash_bytes = match algorithm {
241 HashAlgorithm::Sha256 => hash_sha256(data.as_bytes()),
242 HashAlgorithm::Sha512 => hash_sha512(data.as_bytes()),
243 HashAlgorithm::Sha384 => hash_sha384(data.as_bytes()),
244 HashAlgorithm::Sha1 => hash_sha1(data.as_bytes()),
245 HashAlgorithm::Md5 => hash_md5(data.as_bytes()),
246 HashAlgorithm::Keccak => hash_keccak(data.as_bytes()),
247 };
248
249 Ok(encode_bytes(&hash_bytes, digest))
250}
251
252fn hash_sha256(data: &[u8]) -> Vec<u8> {
254 let mut hasher = Sha256::new();
255 hasher.update(data);
256 hasher.finalize().to_vec()
257}
258
259fn hash_sha512(data: &[u8]) -> Vec<u8> {
261 let mut hasher = Sha512::new();
262 hasher.update(data);
263 hasher.finalize().to_vec()
264}
265
266fn hash_sha384(data: &[u8]) -> Vec<u8> {
268 let mut hasher = Sha384::new();
269 hasher.update(data);
270 hasher.finalize().to_vec()
271}
272
273fn hash_sha1(data: &[u8]) -> Vec<u8> {
275 let mut hasher = Sha1::new();
276 hasher.update(data);
277 hasher.finalize().to_vec()
278}
279
280fn hash_md5(data: &[u8]) -> Vec<u8> {
282 use md5::{Digest, Md5};
283 let mut hasher = Md5::new();
284 hasher.update(data);
285 hasher.finalize().to_vec()
286}
287
288fn hash_keccak(data: &[u8]) -> Vec<u8> {
290 let mut hasher = Keccak256::new();
291 hasher.update(data);
292 hasher.finalize().to_vec()
293}
294
295pub fn eddsa_sign(data: &str, secret_key: &[u8]) -> Result<String> {
364 use ed25519_dalek::{Signature, Signer, SigningKey};
365
366 if secret_key.len() != 32 {
367 return Err(Error::invalid_argument(format!(
368 "Ed25519 secret key must be 32 bytes, got {}",
369 secret_key.len()
370 )));
371 }
372
373 let signing_key = SigningKey::from_bytes(
374 secret_key
375 .try_into()
376 .map_err(|_| Error::invalid_argument("Invalid Ed25519 key".to_string()))?,
377 );
378
379 let signature: Signature = signing_key.sign(data.as_bytes());
380 let encoded = general_purpose::STANDARD.encode(signature.to_bytes());
381
382 Ok(base64_to_base64url(&encoded, true))
383}
384
385pub fn jwt_sign(
412 payload: &serde_json::Value,
413 secret: &str,
414 algorithm: HashAlgorithm,
415 header_options: Option<serde_json::Map<String, serde_json::Value>>,
416) -> Result<String> {
417 let mut header = serde_json::Map::new();
418 header.insert(
419 "alg".to_string(),
420 serde_json::Value::String("HS256".to_string()),
421 );
422 header.insert(
423 "typ".to_string(),
424 serde_json::Value::String("JWT".to_string()),
425 );
426
427 if let Some(options) = header_options {
428 for (key, value) in options {
429 header.insert(key, value);
430 }
431 }
432
433 let header_json = serde_json::to_string(&header)?;
434 let payload_json = serde_json::to_string(payload)?;
435
436 let encoded_header = general_purpose::URL_SAFE_NO_PAD.encode(header_json.as_bytes());
437 let encoded_payload = general_purpose::URL_SAFE_NO_PAD.encode(payload_json.as_bytes());
438
439 let token = format!("{encoded_header}.{encoded_payload}");
440
441 let signature = hmac_sign(&token, secret, algorithm, DigestFormat::Base64)?;
442
443 let signature_url = base64_to_base64url(&signature, true);
444
445 Ok(format!("{token}.{signature_url}"))
446}
447
448fn encode_bytes(bytes: &[u8], format: DigestFormat) -> String {
450 match format {
451 DigestFormat::Hex => hex::encode(bytes),
452 DigestFormat::Base64 => general_purpose::STANDARD.encode(bytes),
453 DigestFormat::Binary => String::from_utf8_lossy(bytes).to_string(),
454 }
455}
456
457pub fn base64_to_base64url(base64_str: &str, strip_padding: bool) -> String {
466 let mut result = base64_str.replace('+', "-").replace('/', "_");
467 if strip_padding {
468 result = result.trim_end_matches('=').to_string();
469 }
470 result
471}
472
473pub fn base64url_decode(base64url: &str) -> Result<Vec<u8>> {
484 let base64 = base64url.replace('-', "+").replace('_', "/");
485
486 let padding = match base64.len() % 4 {
487 2 => "==",
488 3 => "=",
489 _ => "",
490 };
491 let base64_padded = format!("{base64}{padding}");
492
493 general_purpose::STANDARD
494 .decode(base64_padded.as_bytes())
495 .map_err(|e| Error::invalid_argument(format!("Base64 decode error: {e}")))
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501
502 #[test]
503 fn test_hmac_sha256_hex() {
504 let result = hmac_sign("test", "secret", HashAlgorithm::Sha256, DigestFormat::Hex).unwrap();
505 assert_eq!(
506 result,
507 "0329a06b62cd16b33eb6792be8c60b158d89a2ee3a876fce9a881ebb488c0914"
508 );
509 }
510
511 #[test]
512 fn test_hmac_sha256_base64() {
513 let result = hmac_sign(
514 "test",
515 "secret",
516 HashAlgorithm::Sha256,
517 DigestFormat::Base64,
518 )
519 .unwrap();
520 assert!(!result.is_empty());
521 }
522
523 #[test]
524 fn test_hash_sha256() {
525 let result = hash("test", HashAlgorithm::Sha256, DigestFormat::Hex).unwrap();
526 assert_eq!(
527 result,
528 "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
529 );
530 }
531
532 #[test]
533 fn test_hash_keccak() {
534 let result = hash("test", HashAlgorithm::Keccak, DigestFormat::Hex).unwrap();
535 assert_eq!(result.len(), 64); }
537
538 #[test]
539 fn test_base64_to_base64url() {
540 let base64 = "abc+def/ghi==";
541 let base64url = base64_to_base64url(base64, true);
542 assert_eq!(base64url, "abc-def_ghi");
543 }
544
545 #[test]
546 fn test_base64url_decode() {
547 let base64url = "abc-def_ghg";
548 let decoded = base64url_decode(base64url).unwrap();
549 assert!(!decoded.is_empty());
550 }
551
552 #[test]
553 fn test_jwt_sign() {
554 use serde_json::json;
555
556 let payload = json!({
557 "user_id": "123",
558 "exp": 1234567890
559 });
560
561 let token = jwt_sign(&payload, "secret", HashAlgorithm::Sha256, None).unwrap();
562
563 let parts: Vec<&str> = token.split('.').collect();
565 assert_eq!(parts.len(), 3);
566 }
567
568 #[test]
569 fn test_hash_algorithm_from_str() {
570 assert_eq!(
571 HashAlgorithm::from_str("sha256").unwrap(),
572 HashAlgorithm::Sha256
573 );
574 assert_eq!(
575 HashAlgorithm::from_str("SHA256").unwrap(),
576 HashAlgorithm::Sha256
577 );
578 assert!(HashAlgorithm::from_str("invalid").is_err());
579 }
580
581 #[test]
582 fn test_digest_format_from_str() {
583 assert_eq!(DigestFormat::from_str("hex"), DigestFormat::Hex);
584 assert_eq!(DigestFormat::from_str("base64"), DigestFormat::Base64);
585 assert_eq!(DigestFormat::from_str("binary"), DigestFormat::Binary);
586 assert_eq!(DigestFormat::from_str("unknown"), DigestFormat::Hex); }
588
589 #[test]
590 fn test_hmac_sha512() {
591 let result = hmac_sign("test", "secret", HashAlgorithm::Sha512, DigestFormat::Hex).unwrap();
592 assert_eq!(result.len(), 128); }
594
595 #[test]
596 fn test_hash_md5() {
597 let result = hash("test", HashAlgorithm::Md5, DigestFormat::Hex).unwrap();
598 assert_eq!(result.len(), 32); }
600}