Skip to main content

mysql_common/
scramble.rs

1// Copyright (c) 2016 Anatoly Ikorsky
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9use sha1::Sha1;
10use sha2::{Digest, Sha256};
11
12fn xor<T, U>(mut left: T, right: U) -> T
13where
14    T: AsMut<[u8]>,
15    U: AsRef<[u8]>,
16{
17    left.as_mut()
18        .iter_mut()
19        .zip(right.as_ref().iter())
20        .map(|(l, r)| *l ^= r)
21        .last();
22    left
23}
24
25fn to_u8_32(bytes: impl AsRef<[u8]>) -> [u8; 32] {
26    let mut out = [0; 32];
27    out[..].copy_from_slice(bytes.as_ref());
28    out
29}
30
31/// Insecure password hasing used in mysql_old_password.
32fn hash_password(output: &mut [u32; 2], password: &[u8]) {
33    let mut nr: u32 = 1345345333;
34    let mut add: u32 = 7;
35    let mut nr2: u32 = 0x12345671;
36
37    let mut tmp: u32;
38
39    for x in password {
40        if *x == b' ' || *x == b'\t' {
41            continue;
42        }
43
44        tmp = *x as u32;
45        nr ^= (nr & 63)
46            .wrapping_add(add)
47            .wrapping_mul(tmp)
48            .wrapping_add(nr << 8);
49        nr2 = nr2.wrapping_add((nr2 << 8) ^ nr);
50        add = add.wrapping_add(tmp);
51    }
52
53    output[0] = nr & 0b01111111_11111111_11111111_11111111;
54    output[1] = nr2 & 0b01111111_11111111_11111111_11111111;
55}
56
57pub fn scramble_323(nonce: &[u8], password: &[u8]) -> Option<[u8; 8]> {
58    struct Rand323 {
59        seed1: u32,
60        seed2: u32,
61        max_value: u32,
62        max_value_dbl: f64,
63    }
64
65    impl Rand323 {
66        fn init(seed1: u32, seed2: u32) -> Self {
67            Self {
68                max_value: 0x3FFFFFFF,
69                max_value_dbl: 0x3FFFFFFF as f64,
70                seed1: seed1 % 0x3FFFFFFF,
71                seed2: seed2 % 0x3FFFFFFF,
72            }
73        }
74
75        fn my_rnd(&mut self) -> f64 {
76            self.seed1 = (self.seed1 * 3 + self.seed2) % self.max_value;
77            self.seed2 = (self.seed1 + self.seed2 + 33) % self.max_value;
78            (self.seed1 as f64) / self.max_value_dbl
79        }
80    }
81
82    let mut hash_pass = [0_u32; 2];
83    let mut hash_message = [0_u32; 2];
84
85    if password.is_empty() {
86        return None;
87    }
88
89    let mut output = [0_u8; 8];
90
91    hash_password(&mut hash_pass, password);
92    hash_password(&mut hash_message, nonce);
93
94    let mut rand_st = Rand323::init(
95        hash_pass[0] ^ hash_message[0],
96        hash_pass[1] ^ hash_message[1],
97    );
98
99    for x in output.iter_mut() {
100        *x = ((rand_st.my_rnd() * 31_f64).floor() + 64_f64) as u8;
101    }
102
103    let extra = (rand_st.my_rnd() * 31_f64).floor() as u8;
104
105    for x in output.iter_mut() {
106        *x ^= extra;
107    }
108
109    Some(output)
110}
111
112/// Scramble algorithm used in mysql_native_password.
113///
114/// SHA1(password) XOR SHA1(nonce, SHA1(SHA1(password)))
115pub fn scramble_native(nonce: &[u8], password: &[u8]) -> Option<[u8; 20]> {
116    fn sha1_1(bytes: impl AsRef<[u8]>) -> [u8; 20] {
117        Sha1::digest(bytes).into()
118    }
119
120    fn sha1_2(bytes1: impl AsRef<[u8]>, bytes2: impl AsRef<[u8]>) -> [u8; 20] {
121        let mut hasher = Sha1::new();
122        hasher.update(bytes1.as_ref());
123        hasher.update(bytes2.as_ref());
124        hasher.finalize().into()
125    }
126
127    if password.is_empty() {
128        return None;
129    }
130
131    Some(xor(
132        sha1_1(password),
133        sha1_2(nonce, sha1_1(sha1_1(password))),
134    ))
135}
136
137/// Scramble algorithm used in cached_sha2_password fast path.
138///
139/// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), nonce))
140pub fn scramble_sha256(nonce: &[u8], password: &[u8]) -> Option<[u8; 32]> {
141    fn sha256_1(bytes: impl AsRef<[u8]>) -> [u8; 32] {
142        let mut hasher = Sha256::default();
143        hasher.update(bytes.as_ref());
144        to_u8_32(hasher.finalize())
145    }
146
147    fn sha256_2(bytes1: impl AsRef<[u8]>, bytes2: impl AsRef<[u8]>) -> [u8; 32] {
148        let mut hasher = Sha256::default();
149        hasher.update(bytes1.as_ref());
150        hasher.update(bytes2.as_ref());
151        to_u8_32(hasher.finalize())
152    }
153
154    if password.is_empty() {
155        return None;
156    }
157
158    Some(xor(
159        sha256_1(password),
160        sha256_2(sha256_1(sha256_1(password)), nonce),
161    ))
162}
163
164/// Crafting a signature according to EdDSA for message using the pass.
165///
166/// This will only work if `client_ed25519` feature is enabled and otherwise will panic at runtime.
167#[cfg_attr(docsrs, doc(cfg(feature = "client_ed25519")))]
168pub fn create_response_for_ed25519(_pass: &[u8], _message: &[u8]) -> [u8; 64] {
169    #[cfg(not(feature = "client_ed25519"))]
170    {
171        panic!(
172            "Can't create response for `ed25519` authentication plugin — `mysql_common/client_ed25519` feature is disabled."
173        )
174    }
175
176    #[cfg(feature = "client_ed25519")]
177    {
178        use curve25519_dalek::scalar::clamp_integer;
179        use curve25519_dalek::{EdwardsPoint, Scalar};
180        use sha2::Sha512;
181        use std::convert::TryInto;
182
183        // Following reference implementation at https://github.com/mysql-net/MySqlConnector/blob/master/src/MySqlConnector.Authentication.Ed25519/Ed25519AuthenticationPlugin.cs#L35
184        // with additional guidance from https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5
185        // Relying on functions provided by curve25519_dalek
186
187        // hash the provided password
188        let hashed_pass = Sha512::default().chain_update(_pass).finalize();
189
190        // the hashed password is split into secret and hash_prefix
191        let secret: &[u8; 32] = &hashed_pass[..32].try_into().unwrap();
192        let hash_prefix: &[u8; 32] = &hashed_pass[32..].try_into().unwrap();
193
194        // The public key A is the encoding of the point [s]B, where s is the clamped secret
195        let small_s = clamp_integer(*secret);
196        let reduced_small_s = Scalar::from_bytes_mod_order(small_s);
197        let capital_a = EdwardsPoint::mul_base(&reduced_small_s).compress();
198
199        // Compute SHA-512(dom2(F, C) || prefix || PH(M)),
200        // dom2 is the empty string,
201        // PH(M) = M is the provided message
202        let small_r = Sha512::default()
203            .chain_update(hash_prefix)
204            .chain_update(_message)
205            .finalize();
206
207        // Interpret the 64-octet digest as a little-endian integer r.
208        let reduced_small_r = Scalar::from_bytes_mod_order_wide(small_r.as_ref());
209
210        // Let R be the encoding of the point [r]B
211        let capital_r = EdwardsPoint::mul_base(&reduced_small_r).compress();
212
213        // Compute SHA512(dom2(F, C) || R || A || PH(M))
214        // dom2 is the empty string,
215        // PH(M) = M is the provided message
216        let small_k = Sha512::default()
217            .chain_update(&capital_r.to_bytes())
218            .chain_update(&capital_a.to_bytes())
219            .chain_update(_message)
220            .finalize();
221
222        // interpret the 64-octet-digest as a little-endian integer k.
223        let reduced_small_k = Scalar::from_bytes_mod_order_wide(small_k.as_ref());
224
225        let capital_s = reduced_small_k * reduced_small_s;
226        let capital_s = capital_s + reduced_small_r;
227
228        // Form the signature of the concatenation of R (32 octets) and the
229        // little-endian encoding of S (32 octets; the three most
230        // significant bits of the final octet are always zero).
231
232        let mut result = [0; 64];
233        result[..32].copy_from_slice(capital_r.as_bytes());
234        result[32..].copy_from_slice(capital_s.as_bytes());
235
236        result
237    }
238}
239
240/// Crafting response message for parsec authentication. It consists of a client nonce and signature.
241///
242/// This will only work if `parsec` feature is enabled and otherwise will panic at runtime.
243#[cfg_attr(docsrs, doc(cfg(feature = "client_parsec")))]
244pub fn create_response_for_parsec(
245    _pass: &[u8],
246    _server_nonce: &[u8; 32],
247    _iterations: u32,
248    _server_salt: &[u8],
249) -> [u8; 96] {
250    #[cfg(not(feature = "client_parsec"))]
251    {
252        panic!(
253            "Can't create response for `parsec` authentication plugin — `mysql_common/client_parsec` feature is disabled."
254        )
255    }
256
257    #[cfg(feature = "client_parsec")]
258    {
259        use ed25519_dalek::{Signer, SigningKey};
260        use pbkdf2::pbkdf2_hmac;
261        use rand::Rng;
262        use sha2::Sha512;
263
264        // Generating derived key using iterations and user specific salt received in parsec ext-salt packet
265        let mut derived_key = [0u8; 32];
266        pbkdf2_hmac::<Sha512>(_pass, _server_salt, _iterations, &mut derived_key);
267
268        let mut rng = rand::rng();
269        // This is the server nonce[0..32] + client nonce[32..64] + signature buffer[64..128]
270        // [0..64] - is the message we will need to sign. [32..] - (client nonce + signature) that will be sent to server
271        let mut msg_and_signature = [0u8; 128];
272        msg_and_signature[..32].copy_from_slice(_server_nonce);
273        // First 32 bytes is random client nonce
274        let client_nonce = &mut msg_and_signature[32..64];
275        rng.fill(client_nonce);
276
277        // The rest 64 bytes is the ed25519 signature of server_nonce + client_nonce using derived key
278        let signing_key = SigningKey::from_bytes(&derived_key);
279
280        let signature = signing_key.sign(&msg_and_signature[..64]);
281        msg_and_signature[64..].copy_from_slice(&signature.to_bytes());
282        msg_and_signature[32..].try_into().expect("infallible")
283    }
284}
285
286#[cfg(test)]
287mod test {
288    use super::*;
289
290    #[test]
291    fn should_compute_scrambled_password() {
292        let scr = [
293            0x4e, 0x52, 0x33, 0x48, 0x50, 0x3a, 0x71, 0x49, 0x59, 0x61, 0x5f, 0x39, 0x3d, 0x64,
294            0x62, 0x3f, 0x53, 0x64, 0x7b, 0x60,
295        ];
296        let password = [0x47, 0x21, 0x69, 0x64, 0x65, 0x72, 0x32, 0x37];
297        let output1 = scramble_native(&scr, &password);
298        let output2 = scramble_sha256(&scr, &password);
299        assert!(output1.is_some());
300        assert!(output2.is_some());
301        assert_eq!(
302            output1.unwrap(),
303            [
304                0x09, 0xcf, 0xf8, 0x85, 0x5e, 0x9e, 0x70, 0x53, 0x40, 0xff, 0x22, 0x70, 0xd8, 0xfb,
305                0x9f, 0xad, 0xba, 0x90, 0x6b, 0x70,
306            ]
307        );
308        assert_eq!(
309            output2.unwrap(),
310            [
311                0x4f, 0x97, 0xbb, 0xfd, 0x20, 0x24, 0x01, 0xc4, 0x2a, 0x69, 0xde, 0xaa, 0xe5, 0x3b,
312                0xda, 0x07, 0x7e, 0xd7, 0x57, 0x85, 0x63, 0xc1, 0xa8, 0x0e, 0xb8, 0x16, 0xc8, 0x21,
313                0x19, 0xb6, 0x8d, 0x2e,
314            ]
315        );
316    }
317}