char-slop/tidal-dl

tidal ripper

git clone https://git.t4t.associates/char-slop/tidal-dl

Charlotte Somsimplify3841d9b

main
2.9 KiB76 linesraw
1use aes::{Aes128, Aes256};
2use anyhow::{Context, Result};
3use cbc::Decryptor;
4use cipher::{block_padding::NoPadding, BlockDecryptMut, KeyIvInit, StreamCipher};
5
6const MASTER_KEY: [u8; 32] = *include_bytes!("../assets/master.key.bin");
7
8/// Split a TIDAL `keyId` security token into the (key, nonce) pair.
9pub fn decrypt_security_token(token: &str) -> Result<([u8; 16], [u8; 8])> {
10    use base64::{engine::general_purpose::STANDARD, Engine as _};
11    let bytes = STANDARD.decode(token).context("security token base64")?;
12    anyhow::ensure!(bytes.len() > 16 && (bytes.len() - 16) % 16 == 0, "bad security token length");
13    let (iv, cipher) = bytes.split_at(16);
14    let mut buf = cipher.to_vec();
15    Decryptor::<Aes256>::new_from_slices(&MASTER_KEY, iv)
16        .map_err(|e| anyhow::anyhow!("key/iv: {e}"))?
17        .decrypt_padded_mut::<NoPadding>(&mut buf)
18        .map_err(|e| anyhow::anyhow!("security token decrypt: {e}"))?;
19    anyhow::ensure!(buf.len() >= 24, "decrypted token too short");
20    Ok((buf[..16].try_into().unwrap(), buf[16..24].try_into().unwrap()))
21}
22
23/// AES-128-CTR decrypt (nonce || big-endian counter, matching pycryptodome's
24/// `Counter.new(64, prefix=nonce)`).
25pub fn decrypt_bytes(data: Vec<u8>, key: &[u8; 16], nonce: &[u8; 8]) -> Vec<u8> {
26    let mut iv = [0u8; 16];
27    iv[..8].copy_from_slice(nonce);
28    let mut cipher = ctr::Ctr128BE::<Aes128>::new_from_slices(key, &iv).expect("ctr key/iv");
29    let mut buf = data;
30    cipher.apply_keystream(&mut buf);
31    buf
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use cipher::{block_padding::NoPadding, BlockEncryptMut};
38
39    #[test]
40    fn security_token_round_trip() {
41        let key = *b"0123456789abcdef";
42        let nonce = *b"12345678";
43        let mut plain = Vec::new();
44        plain.extend_from_slice(&key);
45        plain.extend_from_slice(&nonce);
46        plain.extend_from_slice(&[0u8; 8]);
47        let iv = [7u8; 16];
48        let mut buf = plain.clone();
49        cbc::Encryptor::<Aes256>::new_from_slices(&MASTER_KEY, &iv)
50            .unwrap()
51            .encrypt_padded_mut::<NoPadding>(&mut buf, plain.len())
52            .unwrap();
53        let mut token_raw = iv.to_vec();
54        token_raw.extend_from_slice(&buf);
55        use base64::{engine::general_purpose::STANDARD, Engine as _};
56        let (k, n) = decrypt_security_token(&STANDARD.encode(token_raw)).unwrap();
57        assert_eq!(k, key);
58        assert_eq!(n, nonce);
59    }
60
61    #[test]
62    fn ctr_round_trip() {
63        let key = *b"0123456789abcdef";
64        let nonce = *b"12345678";
65        let data = b"flac audio bytes...".to_vec();
66        let enc = {
67            let mut iv = [0u8; 16];
68            iv[..8].copy_from_slice(&nonce);
69            let mut c = ctr::Ctr128BE::<Aes128>::new((&key).into(), (&iv).into());
70            let mut b = data.clone();
71            c.apply_keystream(&mut b);
72            b
73        };
74        assert_eq!(decrypt_bytes(enc, &key, &nonce), data);
75    }
76}