Files
HackBrowserData/crypto/crypto.go
T

215 lines
6.4 KiB
Go

package crypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/sha1"
"fmt"
)
// AESCBCEncrypt encrypts data using AES-CBC mode with PKCS5 padding.
// Supports all AES key sizes: 16 bytes (AES-128), 24 bytes (AES-192), or 32 bytes (AES-256).
func AESCBCEncrypt(key, iv, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cbcEncrypt(block, iv, plaintext)
}
// AESCBCDecrypt decrypts data using AES-CBC mode with PKCS5 unpadding.
// Supports all AES key sizes: 16 bytes (AES-128), 24 bytes (AES-192), or 32 bytes (AES-256).
func AESCBCDecrypt(key, iv, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cbcDecrypt(block, iv, ciphertext)
}
// DES3Encrypt encrypts data using 3DES-CBC mode with PKCS5 padding.
func DES3Encrypt(key, iv, plaintext []byte) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
return cbcEncrypt(block, iv, plaintext)
}
// DES3Decrypt decrypts data using 3DES-CBC mode with PKCS5 unpadding.
func DES3Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
return cbcDecrypt(block, iv, ciphertext)
}
// gcmNonceSize is the AES-GCM standard nonce size used by Chromium's v10/v20
// cipher formats. Cross-platform because the v20 ciphertext layout is the
// same regardless of host OS (only Windows currently produces v20).
const gcmNonceSize = 12
// chromiumCBCIV is the fixed IV Chromium uses for AES-CBC v10/v11 (macOS/Linux).
var chromiumCBCIV = bytes.Repeat([]byte{0x20}, aes.BlockSize)
// kEmptyKey is Chromium's decrypt-only fallback for data corrupted by a KWallet
// race in Chrome ~89 (crbug.com/40055416). Matches kEmptyKey in os_crypt_linux.cc.
var kEmptyKey = PBKDF2Key([]byte(""), []byte("saltysalt"), 1, 16, sha1.New)
// DecryptChromiumGCM decrypts a prefixed AES-GCM blob: version(3B)+nonce(12B)+ct+tag.
// Used by Windows v10 (AES-256) and v20; the layout is identical and platform-neutral.
func DecryptChromiumGCM(key, ciphertext []byte) ([]byte, error) {
if len(ciphertext) < versionPrefixLen+gcmNonceSize {
return nil, errShortCiphertext
}
nonce := ciphertext[versionPrefixLen : versionPrefixLen+gcmNonceSize]
payload := ciphertext[versionPrefixLen+gcmNonceSize:]
return AESGCMDecrypt(key, nonce, payload)
}
// DecryptChromiumCBC decrypts a prefixed AES-CBC blob (version(3B)+ct) with Chromium's
// fixed IV, retrying with kEmptyKey to recover crbug.com/40055416 KWallet-corrupted data.
// Used by macOS/Linux v10 and Linux v11 (both AES-128).
func DecryptChromiumCBC(key, ciphertext []byte) ([]byte, error) {
if len(ciphertext) < versionPrefixLen+aes.BlockSize {
return nil, errShortCiphertext
}
payload := ciphertext[versionPrefixLen:]
plaintext, err := AESCBCDecrypt(key, chromiumCBCIV, payload)
if err == nil {
return plaintext, nil
}
if alt, altErr := AESCBCDecrypt(kEmptyKey, chromiumCBCIV, payload); altErr == nil {
return alt, nil
}
return nil, err
}
// AESGCMEncrypt encrypts data using AES-GCM mode.
func AESGCMEncrypt(key, nonce, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aead, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
if len(nonce) != aead.NonceSize() {
return nil, errInvalidNonceLen
}
return aead.Seal(nil, nonce, plaintext, nil), nil
}
// AESGCMDecrypt decrypts data using AES-GCM mode.
func AESGCMDecrypt(key, nonce, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aead, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
if len(nonce) != aead.NonceSize() {
return nil, errInvalidNonceLen
}
return aead.Open(nil, nonce, ciphertext, nil)
}
// AESGCMDecryptBlob decrypts a blob shaped as [12B nonce][ciphertext+16B GCM tag] with caller-supplied AAD.
// Used by protocols that wrap AES-GCM output with a fixed-length nonce prefix (Yandex passwords/cards).
func AESGCMDecryptBlob(key, blob, aad []byte) ([]byte, error) {
if len(blob) < gcmNonceSize {
return nil, errShortCiphertext
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aead, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
return aead.Open(nil, blob[:gcmNonceSize], blob[gcmNonceSize:], aad)
}
// cbcEncrypt adds PKCS5 padding and encrypts plaintext in CBC mode.
func cbcEncrypt(block cipher.Block, iv, plaintext []byte) ([]byte, error) {
if len(iv) != block.BlockSize() {
return nil, errInvalidIVLength
}
padded := pkcs5Padding(plaintext, block.BlockSize())
dst := make([]byte, len(padded))
cipher.NewCBCEncrypter(block, iv).CryptBlocks(dst, padded)
return dst, nil
}
// cbcDecrypt decrypts ciphertext in CBC mode and removes PKCS5 padding.
func cbcDecrypt(block cipher.Block, iv, ciphertext []byte) ([]byte, error) {
bs := block.BlockSize()
if len(iv) != bs {
return nil, errInvalidIVLength
}
if len(ciphertext) < bs {
return nil, errShortCiphertext
}
if len(ciphertext)%bs != 0 {
return nil, errInvalidBlockSize
}
dst := make([]byte, len(ciphertext))
cipher.NewCBCDecrypter(block, iv).CryptBlocks(dst, ciphertext)
dst, err := pkcs5UnPadding(dst, bs)
if err != nil {
return nil, fmt.Errorf("decrypt: %w", err)
}
return dst, nil
}
// paddingZero pads src with zero bytes to the given length.
// Returns src unchanged if already long enough; otherwise returns a new slice.
func paddingZero(src []byte, length int) []byte {
if len(src) >= length {
return src
}
dst := make([]byte, length)
copy(dst, src)
return dst
}
// pkcs5Padding adds PKCS5/PKCS7 padding to src.
// Always returns a new slice; never modifies src.
func pkcs5Padding(src []byte, blockSize int) []byte {
n := blockSize - (len(src) % blockSize)
dst := make([]byte, len(src)+n)
copy(dst, src)
for i := len(src); i < len(dst); i++ {
dst[i] = byte(n)
}
return dst
}
// pkcs5UnPadding removes PKCS5/PKCS7 padding from src.
func pkcs5UnPadding(src []byte, blockSize int) ([]byte, error) {
length := len(src)
if length == 0 {
return nil, errInvalidPadding
}
padding := int(src[length-1])
if padding < 1 || padding > blockSize || padding > length {
return nil, errInvalidPadding
}
for _, b := range src[length-padding:] {
if int(b) != padding {
return nil, errInvalidPadding
}
}
return src[:length-padding], nil
}