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#[cfg(test)]
165mod test {
166    use super::*;
167
168    #[test]
169    fn should_compute_scrambled_password() {
170        let scr = [
171            0x4e, 0x52, 0x33, 0x48, 0x50, 0x3a, 0x71, 0x49, 0x59, 0x61, 0x5f, 0x39, 0x3d, 0x64,
172            0x62, 0x3f, 0x53, 0x64, 0x7b, 0x60,
173        ];
174        let password = [0x47, 0x21, 0x69, 0x64, 0x65, 0x72, 0x32, 0x37];
175        let output1 = scramble_native(&scr, &password);
176        let output2 = scramble_sha256(&scr, &password);
177        assert!(output1.is_some());
178        assert!(output2.is_some());
179        assert_eq!(
180            output1.unwrap(),
181            [
182                0x09, 0xcf, 0xf8, 0x85, 0x5e, 0x9e, 0x70, 0x53, 0x40, 0xff, 0x22, 0x70, 0xd8, 0xfb,
183                0x9f, 0xad, 0xba, 0x90, 0x6b, 0x70,
184            ]
185        );
186        assert_eq!(
187            output2.unwrap(),
188            [
189                0x4f, 0x97, 0xbb, 0xfd, 0x20, 0x24, 0x01, 0xc4, 0x2a, 0x69, 0xde, 0xaa, 0xe5, 0x3b,
190                0xda, 0x07, 0x7e, 0xd7, 0x57, 0x85, 0x63, 0xc1, 0xa8, 0x0e, 0xb8, 0x16, 0xc8, 0x21,
191                0x19, 0xb6, 0x8d, 0x2e,
192            ]
193        );
194    }
195}