Skip to main content

steamkit/util/
crypto_helper.rs

1//! The symmetric crypto used in Steam's protocols.
2
3use aes::cipher::block_padding::Pkcs7;
4use aes::cipher::{BlockDecryptMut, KeyInit, KeyIvInit};
5use aes::cipher::generic_array::GenericArray;
6use cbc::Decryptor as CbcDecryptor;
7use ecb::Decryptor as EcbDecryptor;
8
9use crate::error::SteamError;
10
11/// The size of the AES block, in bytes.
12const BLOCK_SIZE: usize = 16;
13
14/// The size of the AES-256 key, in bytes.
15const KEY_SIZE: usize = 32;
16
17/// Decrypts AES-256-CBC (PKCS7) data whose IV is prepended, ECB-encrypted.
18/// Same as [`CryptoHelper::symmetric_decrypt`].
19pub fn symmetric_decrypt(input: &[u8], key: &[u8]) -> Result<Vec<u8>, SteamError> {
20    CryptoHelper::symmetric_decrypt(input, key)
21}
22
23/// The crypto functions used in Steam's protocols.
24#[derive(Debug)]
25pub struct CryptoHelper;
26
27impl CryptoHelper {
28    /// Decrypts AES-256-CBC (PKCS7) data whose IV is prepended, ECB-encrypted.
29    ///
30    /// The first 16 bytes of `input` are the IV, encrypted with AES-256-ECB
31    /// (no padding); the rest is decrypted with AES-256-CBC using that IV. The
32    /// key has to be 32 bytes.
33    pub fn symmetric_decrypt(input: &[u8], key: &[u8]) -> Result<Vec<u8>, SteamError> {
34        if key.len() != KEY_SIZE {
35            crate::util::debug_log::DebugLog::assert(
36                false,
37                "CryptoHelper",
38                "SymmetricDecrypt used with non 32 byte key!",
39            );
40
41            return Err(SteamError::InvalidArgument(format!(
42                "SymmetricDecrypt used with non {KEY_SIZE} byte key (was {} bytes)!",
43                key.len()
44            )));
45        }
46
47        if input.len() < BLOCK_SIZE {
48            return Err(SteamError::Crypto(format!(
49                "SymmetricDecrypt input is too short to contain the IV (was {} bytes).",
50                input.len()
51            )));
52        }
53
54        // The first 16 bytes are the ECB-encrypted IV.
55        let mut iv = [0u8; BLOCK_SIZE];
56        {
57            let mut decryptor = EcbDecryptor::<aes::Aes256>::new_from_slice(key)
58                .map_err(|_| SteamError::Crypto("Invalid AES-256 key.".to_string()))?;
59
60            let mut block = GenericArray::clone_from_slice(&input[..BLOCK_SIZE]);
61            decryptor.decrypt_block_mut(&mut block);
62            iv.copy_from_slice(block.as_slice());
63        }
64
65        // The rest is CBC-encrypted with that IV, PKCS7-padded.
66        let ciphertext = &input[BLOCK_SIZE..];
67
68        if ciphertext.is_empty() || ciphertext.len() % BLOCK_SIZE != 0 {
69            return Err(SteamError::Crypto(format!(
70                "SymmetricDecrypt input has an invalid length (was {} bytes).",
71                input.len()
72            )));
73        }
74
75        let mut buffer = vec![0u8; ciphertext.len()];
76        buffer.copy_from_slice(ciphertext);
77
78        let decryptor = CbcDecryptor::<aes::Aes256>::new_from_slices(key, &iv)
79            .map_err(|_| SteamError::Crypto("Invalid AES-256 key or IV.".to_string()))?;
80
81        let plaintext = decryptor
82            .decrypt_padded_mut::<Pkcs7>(&mut buffer)
83            .map_err(|_| SteamError::Crypto("Failed to decrypt the payload.".to_string()))?;
84
85        Ok(plaintext.to_vec())
86    }
87}
88