metamorphic_crypto/
b64.rs1use base64::{Engine as _, engine::general_purpose::STANDARD};
6
7use crate::CryptoError;
8
9#[inline]
11pub fn encode(bytes: &[u8]) -> String {
12 STANDARD.encode(bytes)
13}
14
15#[inline]
17pub fn decode(s: &str) -> Result<Vec<u8>, CryptoError> {
18 STANDARD.decode(s).map_err(CryptoError::Base64)
19}
20
21pub fn parse_salt_from_key_hash(key_hash: &str) -> Result<&str, CryptoError> {
23 let (salt, rest) = key_hash
24 .split_once('$')
25 .ok_or(CryptoError::InvalidKeyHash)?;
26 if rest.contains('$') {
27 return Err(CryptoError::InvalidKeyHash);
28 }
29 Ok(salt)
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35
36 #[test]
37 fn roundtrip() {
38 let data = b"hello world";
39 let encoded = encode(data);
40 assert_eq!(decode(&encoded).unwrap(), data);
41 }
42
43 #[test]
44 fn matches_js_btoa() {
45 assert_eq!(encode(b"hello"), "aGVsbG8=");
47 assert_eq!(decode("aGVsbG8=").unwrap(), b"hello");
48 }
49
50 #[test]
51 fn parse_salt_valid() {
52 let salt = parse_salt_from_key_hash("c2FsdA==$argon2id").unwrap();
53 assert_eq!(salt, "c2FsdA==");
54 }
55
56 #[test]
57 fn parse_salt_invalid() {
58 assert!(parse_salt_from_key_hash("noseparator").is_err());
59 assert!(parse_salt_from_key_hash("a$b$c").is_err());
60 }
61}