tiger_pkg/
crypto.rs

1use std::collections::HashMap;
2
3use aes_gcm::{AeadInPlace, Aes128Gcm, KeyInit};
4use lazy_static::lazy_static;
5use parking_lot::RwLock;
6use tracing::{error, info};
7
8use crate::{DestinyVersion, GameVersion, Version};
9
10lazy_static! {
11    static ref CIPHERS_EXTRA: RwLock<HashMap<u64, (Aes128Gcm, [u8; 12])>> = {
12        if let Ok(keyfile) = std::fs::read_to_string("keys.txt") {
13            let k: HashMap<u64, (Aes128Gcm, [u8; 12])> = parse_keys(&keyfile)
14                .into_iter()
15                .map(|(group, key, iv)| (group, (Aes128Gcm::new(&key.into()), iv)))
16                .collect();
17
18            if !k.is_empty() {
19                info!("Loaded {} external keys", k.len());
20            }
21
22            RwLock::new(k)
23        } else {
24            RwLock::new(HashMap::new())
25        }
26    };
27}
28
29pub fn register_pkg_key(group: u64, key: [u8; 16], iv: [u8; 12]) {
30    CIPHERS_EXTRA
31        .write()
32        .insert(group, (Aes128Gcm::new(&key.into()), iv));
33}
34
35pub struct PkgGcmState {
36    nonce: [u8; 12],
37    cipher_0: Aes128Gcm,
38    cipher_1: Aes128Gcm,
39    cipher_extra: Option<(Aes128Gcm, [u8; 12])>,
40    group: u64,
41}
42
43impl PkgGcmState {
44    pub fn new(pkg_id: u16, version: GameVersion, group: u64) -> PkgGcmState {
45        let mut g = PkgGcmState {
46            nonce: version.aes_nonce_base(),
47            cipher_0: Aes128Gcm::new(&version.aes_key_0().into()),
48            cipher_1: Aes128Gcm::new(&version.aes_key_1().into()),
49            cipher_extra: CIPHERS_EXTRA.read().get(&group).cloned(),
50            group,
51        };
52
53        g.shift_nonce(pkg_id, version);
54
55        g
56    }
57
58    fn shift_nonce(&mut self, pkg_id: u16, version: GameVersion) {
59        match version {
60            GameVersion::Destiny(ver) => {
61                self.nonce[0] ^= (pkg_id >> 8) as u8;
62                match ver {
63                    DestinyVersion::Destiny2Beta | DestinyVersion::Destiny2Shadowkeep => {
64                        self.nonce[1] = 0xf9
65                    }
66                    _ => self.nonce[1] = 0xea,
67                }
68                self.nonce[11] ^= pkg_id as u8;
69            }
70            _ => unimplemented!(),
71        }
72    }
73
74    pub fn decrypt_block_in_place(
75        &self,
76        flags: u16,
77        tag: &[u8],
78        data: &mut [u8],
79    ) -> anyhow::Result<()> {
80        if (flags & 0x8) != 0 {
81            if let Some((cipher, iv)) = &self.cipher_extra {
82                if cipher
83                    .decrypt_in_place_detached(iv.as_slice().into(), &[], data, tag.into())
84                    .is_ok()
85                {
86                    return Ok(());
87                }
88            }
89
90            return Err(anyhow::anyhow!(format!(
91                "No (working) key found for PKG group {:016X}",
92                self.group
93            )));
94        }
95
96        let (cipher, nonce) = if (flags & 0x4) != 0 {
97            (&self.cipher_1, &self.nonce)
98        } else {
99            (&self.cipher_0, &self.nonce)
100        };
101
102        match cipher.decrypt_in_place_detached(nonce.into(), &[], data, tag.into()) {
103            Ok(_) => Ok(()),
104            Err(_) => Err(anyhow::anyhow!("Failed to decrypt PKG data block")),
105        }
106    }
107}
108
109// example key `123456789ABCDEF:ABCDA1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D:1234567890ABCDEF // optional comment`
110pub fn parse_keys(data: &str) -> Vec<(u64, [u8; 16], [u8; 12])> {
111    data.lines()
112        .enumerate()
113        .filter_map(|(i, l)| {
114            let mut parts = l.split(':');
115            let Some(group) = parts.next() else {
116                error!("Failed to parse group on line {i}");
117                return None;
118            };
119            let Some(key) = parts.next() else {
120                error!("Failed to parse key on line {i}");
121                return None;
122            };
123            let Some(iv) = parts.next().map(|p| p.chars().take(24).collect::<String>()) else {
124                error!("Failed to parse iv on line {i}");
125                return None;
126            };
127
128            let group = match u64::from_str_radix(group, 16) {
129                Ok(k) => k,
130                Err(e) => {
131                    error!("Failed to parse group on line {i}: {e}");
132                    return None;
133                }
134            };
135
136            let key = match hex::decode(key) {
137                Ok(data) => {
138                    if data.len() != 16 {
139                        error!("Invalid key length on line {i}");
140                        return None;
141                    }
142                    let mut k = [0u8; 16];
143                    k.copy_from_slice(&data);
144                    k
145                }
146                Err(e) => {
147                    error!("Failed to parse key on line {i}: {e}");
148                    return None;
149                }
150            };
151
152            let iv = match hex::decode(iv) {
153                Ok(data) => {
154                    if data.len() != 12 {
155                        error!("Invalid iv length on line {i}");
156                        return None;
157                    }
158                    let mut v = [0u8; 12];
159                    v.copy_from_slice(&data);
160                    v
161                }
162                Err(e) => {
163                    error!("Failed to parse iv on line {i}: {e}");
164                    return None;
165                }
166            };
167
168            Some((group, key, iv))
169        })
170        .collect()
171}