gaussdb_protocol/authentication/
mod.rs

1//! Authentication protocol support.
2use hmac::{Hmac, Mac};
3use md5::{Digest, Md5};
4use pbkdf2::pbkdf2_hmac;
5use sha1::Sha1;
6use sha2::Sha256;
7
8pub mod sasl;
9
10/// Hashes authentication information in a way suitable for use in response
11/// to an `AuthenticationMd5Password` message.
12///
13/// The resulting string should be sent back to the database in a
14/// `PasswordMessage` message.
15#[inline]
16pub fn md5_hash(username: &[u8], password: &[u8], salt: [u8; 4]) -> String {
17    let mut md5 = Md5::new();
18    md5.update(password);
19    md5.update(username);
20    let output = md5.finalize_reset();
21    md5.update(format!("{:x}", output));
22    md5.update(salt);
23    format!("md5{:x}", md5.finalize())
24}
25
26/// Hashes authentication information using SHA256_MD5 for GaussDB/OpenGauss.
27///
28/// This implements the SHA256_MD5 authentication method used by GaussDB and OpenGauss.
29/// The process is: MD5(password + username) -> SHA256(md5_hex + salt) -> "sha256" + hex
30#[inline]
31pub fn sha256_hash(username: &[u8], password: &[u8], salt: &[u8]) -> String {
32    // Step 1: MD5(password + username)
33    let mut md5 = Md5::new();
34    md5.update(password);
35    md5.update(username);
36    let md5_result = md5.finalize();
37    let md5_hex = format!("{:x}", md5_result);
38
39    // Step 2: SHA256(md5_hex + salt)
40    let mut sha256 = Sha256::new();
41    sha256.update(md5_hex.as_bytes());
42    sha256.update(salt);
43    let sha256_result = sha256.finalize();
44
45    format!("sha256{:x}", sha256_result)
46}
47
48/// Hashes authentication information using MD5_SHA256 method for GaussDB/OpenGauss.
49///
50/// This implements the MD5_SHA256 authentication method used by GaussDB and OpenGauss.
51/// The process involves PBKDF2, HMAC-SHA256, and MD5 operations.
52#[inline]
53pub fn md5_sha256_hash(password: &str, random_code: &str, salt: &[u8]) -> String {
54    // Step 1: Generate K using PBKDF2
55    let random_bytes = hex::decode(random_code).unwrap_or_else(|_| random_code.as_bytes().to_vec());
56    let mut k = [0u8; 32];
57    pbkdf2_hmac::<Sha1>(password.as_bytes(), &random_bytes, 2048, &mut k);
58
59    // Step 2: Generate server_key and client_key using HMAC-SHA256
60    let mut server_key_mac =
61        Hmac::<Sha256>::new_from_slice(&k).expect("HMAC can take key of any size");
62    server_key_mac.update(b"Sever Key"); // Note: "Sever" not "Server" - matches GaussDB implementation
63    let server_key = server_key_mac.finalize().into_bytes();
64
65    let mut client_key_mac =
66        Hmac::<Sha256>::new_from_slice(&k).expect("HMAC can take key of any size");
67    client_key_mac.update(b"Client Key");
68    let client_key = client_key_mac.finalize().into_bytes();
69
70    // Step 3: Generate stored_key using SHA256
71    let mut sha256 = Sha256::new();
72    sha256.update(client_key);
73    let stored_key = sha256.finalize();
74
75    // Step 4: Build the encryption string
76    let encrypt_string = format!(
77        "{}{}{}",
78        random_code,
79        hex::encode(server_key),
80        hex::encode(stored_key)
81    );
82
83    // Step 5: MD5(encrypt_string + salt)
84    let mut md5 = Md5::new();
85    md5.update(encrypt_string.as_bytes());
86    md5.update(salt);
87    let md5_result = md5.finalize();
88
89    format!("md5{:x}", md5_result)
90}
91
92#[cfg(test)]
93mod test {
94    use super::*;
95
96    #[test]
97    fn md5() {
98        let username = b"md5_user";
99        let password = b"password";
100        let salt = [0x2a, 0x3d, 0x8f, 0xe0];
101
102        assert_eq!(
103            md5_hash(username, password, salt),
104            "md562af4dd09bbb41884907a838a3233294"
105        );
106    }
107
108    #[test]
109    fn sha256() {
110        let username = b"testuser";
111        let password = b"testpass";
112        let salt = b"salt1234";
113        let result = sha256_hash(username, password, salt);
114        assert!(result.starts_with("sha256"));
115        assert_eq!(result.len(), 70); // "sha256" + 64 hex chars
116    }
117
118    #[test]
119    fn md5_sha256() {
120        let password = "Gaussdb@123";
121        let random_code = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
122        let salt = b"randomsalt";
123        let result = md5_sha256_hash(password, random_code, salt);
124        assert!(result.starts_with("md5"));
125        assert_eq!(result.len(), 35); // "md5" + 32 hex chars
126
127        // Test with known values to ensure consistency
128        let result2 = md5_sha256_hash(password, random_code, salt);
129        assert_eq!(result, result2);
130    }
131
132    #[test]
133    fn gaussdb_authentication_compatibility() {
134        // Test cases based on GaussDB/OpenGauss authentication requirements
135        let test_cases: Vec<(&str, &str, &[u8])> = vec![
136            ("omm", "Enmo@123", b"salt"),
137            ("gaussdb", "Gaussdb@123", b"test_salt"),
138            ("postgres_user", "password", b"random_salt"),
139        ];
140
141        for (username, password, salt) in test_cases {
142            let sha256_result = sha256_hash(username.as_bytes(), password.as_bytes(), salt);
143            let random_code = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
144            let md5_sha256_result = md5_sha256_hash(password, random_code, salt);
145
146            // Both should produce valid hash strings
147            assert!(sha256_result.starts_with("sha256"));
148            assert!(md5_sha256_result.starts_with("md5"));
149            assert_eq!(sha256_result.len(), 70);
150            assert_eq!(md5_sha256_result.len(), 35);
151
152            // They should produce different results (different algorithms)
153            assert_ne!(sha256_result, md5_sha256_result);
154        }
155    }
156}