steam-crypto-rs 0.1.2

Steam encryption and cryptographic utilities (AES-256-CBC, RSA-OAEP, HMAC-IV) for the Steam protocol.
Documentation

steam-crypto-rs

Crates.io Docs.rs License: MIT

Steam encryption and cryptographic utilities for Rust.

This crate provides the cryptographic primitives required for Steam protocol communication, including symmetric encryption (AES-256-CBC) and asymmetric key exchange (RSA-OAEP).

Overview

Feature Description
AES-256-CBC Encryption Symmetric encryption with random or HMAC-derived IV
RSA-OAEP Key Exchange Session key encryption using Steam's public key
Session Key Management 32-byte key generation and management
HMAC-IV Mode Integrity-verified encryption for TCP connections

Installation

[dependencies]
steam-crypto-rs = "0.1"

The crate is imported as steam_crypto in your code (the package name is steam-crypto-rs because steam-crypto is taken on crates.io).

Usage

Session Key Generation

use steam_crypto::SessionKey;

// Generate a new random 32-byte session key
let key = SessionKey::generate();

// Access key components
let aes_key = key.aes_key();   // First 16 bytes for AES
let hmac_key = key.hmac_key(); // Last 16 bytes for HMAC

// Create from existing bytes
let bytes = [0u8; 32];
let key = SessionKey::from_bytes(&bytes).unwrap();

Symmetric Encryption (AES-256-CBC)

use steam_crypto::{SessionKey, encrypt_message, decrypt_message};

let key = SessionKey::generate();
let plaintext = b"Hello, Steam!";

// Encrypt (prepends random 16-byte IV)
let encrypted = encrypt_message(&key, plaintext)?;

// Decrypt (extracts IV from first 16 bytes)
let decrypted = decrypt_message(&key, &encrypted)?;
assert_eq!(decrypted, plaintext);

HMAC-IV Encryption (TCP Mode)

For TCP connections, Steam uses HMAC-derived IVs for message integrity:

use steam_crypto::{SessionKey, encrypt_with_hmac_iv, decrypt_with_hmac_iv};

let key = SessionKey::generate();
let plaintext = b"Secure message";

// Encrypt with HMAC-derived IV
let encrypted = encrypt_with_hmac_iv(&key, plaintext)?;

// Decrypt and verify HMAC integrity
let decrypted = decrypt_with_hmac_iv(&key, &encrypted)?;

RSA Key Exchange (TCP Handshake)

During the TCP ChannelEncrypt handshake:

use steam_crypto::{generate_session_key, calculate_key_crc};

// Server sends a 16-byte nonce
let nonce: [u8; 16] = /* from ChannelEncryptRequest */;

// Generate session key, XOR with nonce, encrypt with Steam's RSA key
let key_pair = generate_session_key(&nonce)?;

// Use the plain key for symmetric encryption
let session_key = key_pair.plain;

// Send encrypted key with CRC to server
let encrypted_key = key_pair.encrypted;
let crc = calculate_key_crc(&encrypted_key);

API Reference

Types

Type Description
SessionKey 32-byte symmetric key with AES and HMAC portions
SessionKeyPair Contains both plain and RSA-encrypted session key
CryptoError Error type for cryptographic operations

Functions

Function Description
encrypt_message(key, plaintext) AES-256-CBC encryption with random IV
decrypt_message(key, ciphertext) AES-256-CBC decryption
encrypt_with_hmac_iv(key, plaintext) Encryption with HMAC-derived IV (TCP mode)
decrypt_with_hmac_iv(key, ciphertext) Decryption with HMAC verification (TCP mode)
generate_session_key(nonce) Generate and RSA-encrypt a session key
calculate_key_crc(encrypted_key) CRC32 checksum for handshake verification

Error Variants

Variant Description
InvalidKeyLength Key is not 32 bytes
EncryptionFailed(String) AES or RSA encryption error
DecryptionFailed(String) Decryption or padding error
InvalidHmac HMAC-IV verification failed
InvalidCrc CRC32 checksum mismatch

Wire Formats

Standard Encryption

┌────────────────┬─────────────────────────────────┐
│    IV (16B)    │  AES-256-CBC Encrypted Data     │
└────────────────┴─────────────────────────────────┘

HMAC-IV Mode

The IV is derived from the plaintext for integrity verification:

IV = HMAC-SHA1(plaintext, hmac_key)[0..13] || random_bytes[0..3]
  • First 13 bytes from HMAC provide integrity
  • Last 3 bytes are random for additional entropy
  • On decrypt, first 13 bytes of IV are verified against computed HMAC

Session Key Structure

┌─────────────────────────────────────────────────────────────────┐
│                    SessionKey (32 bytes)                        │
├─────────────────────────────────┬───────────────────────────────┤
│      AES Key (16 bytes)         │     HMAC Key (16 bytes)       │
│         key[0..16]              │         key[16..32]           │
└─────────────────────────────────┴───────────────────────────────┘

TCP Handshake Flow

┌──────────┐                              ┌──────────┐
│  Client  │                              │  Server  │
└────┬─────┘                              └────┬─────┘
     │                                         │
     │    ◄──── ChannelEncryptRequest ─────    │
     │              (16-byte nonce)            │
     │                                         │
     │         ┌─────────────────────┐         │
     │         │ 1. Generate 32B key │         │
     │         │ 2. XOR key[0..16]   │         │
     │         │    with nonce       │         │
     │         │ 3. RSA-OAEP encrypt │         │
     │         │ 4. Calculate CRC32  │         │
     │         └─────────────────────┘         │
     │                                         │
     │    ──── ChannelEncryptResponse ────►    │
     │      (encrypted_key + CRC32 + zeroes)   │
     │                                         │
     │    ◄──── ChannelEncryptResult ──────    │
     │              (EResult::OK)              │
     │                                         │
     │     ═══ Encrypted Channel Active ═══    │
     ▼                                         ▼

Dependencies

Crate Purpose
aes AES-256 block cipher
cbc CBC mode encryption
rand Cryptographic random number generation
rsa RSA-OAEP encryption
sha1 SHA1 for HMAC and OAEP
hmac HMAC-SHA1 for IV derivation
crc32fast CRC32 checksum calculation
thiserror Error type derivation

Security Notes

  • SessionKey implements Debug to show "SessionKey([REDACTED])" to prevent accidental key logging
  • All random values are generated using cryptographically secure RNG (rand::thread_rng())
  • The RSA public key is hardcoded (Steam's official 1024-bit key)

Source

This crate is a Rust port of node-steam-crypto.

License

MIT