mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-27 19:22:22 +02:00
feat(yandex): password and credit card decryption (#585)
This commit is contained in:
@@ -98,6 +98,23 @@ func AESGCMDecrypt(key, nonce, ciphertext []byte) ([]byte, error) {
|
||||
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() {
|
||||
|
||||
@@ -18,18 +18,6 @@ func DecryptChromium(key, ciphertext []byte) ([]byte, error) {
|
||||
return AESGCMDecrypt(key, nonce, payload)
|
||||
}
|
||||
|
||||
// DecryptYandex decrypts a Yandex-encrypted value.
|
||||
// TODO: Yandex uses the same AES-GCM format as Chromium for now;
|
||||
// update when Yandex-specific decryption diverges.
|
||||
func DecryptYandex(key, ciphertext []byte) ([]byte, error) {
|
||||
if len(ciphertext) < minGCMDataSize {
|
||||
return nil, errShortCiphertext
|
||||
}
|
||||
nonce := ciphertext[versionPrefixLen : versionPrefixLen+gcmNonceSize]
|
||||
payload := ciphertext[versionPrefixLen+gcmNonceSize:]
|
||||
return AESGCMDecrypt(key, nonce, payload)
|
||||
}
|
||||
|
||||
// DecryptDPAPI decrypts a DPAPI-protected blob using the current user's
|
||||
// master key. The actual Win32 call (and its DATA_BLOB / LocalFree dance)
|
||||
// lives in utils/winapi so every package that needs a syscall handle
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// yandexSignature is the protobuf wire-format header (field1 varint=1, field2 len=32) on every wrapped key.
|
||||
var yandexSignature = []byte{0x08, 0x01, 0x12, 0x20}
|
||||
|
||||
var localEncryptorPrefix = []byte("v10")
|
||||
|
||||
const (
|
||||
yandexIntKeyBlobLen = 96 // 12B nonce + 68B ciphertext + 16B GCM tag
|
||||
yandexDataKeyLen = 32
|
||||
)
|
||||
|
||||
var (
|
||||
errYandexMarkerNotFound = errors.New("yandex: v10 marker not found in local_encryptor_data")
|
||||
errYandexBlobShort = errors.New("yandex: encrypted intermediate key truncated")
|
||||
errYandexBadSignature = errors.New("yandex: invalid protobuf signature on decrypted key")
|
||||
errYandexKeyTooShort = errors.New("yandex: decrypted intermediate key shorter than 32 bytes")
|
||||
)
|
||||
|
||||
// DecryptYandexIntermediateKey unwraps the per-DB data key from meta.local_encryptor_data. See RFC-012 §4.2.
|
||||
func DecryptYandexIntermediateKey(masterKey, blob []byte) ([]byte, error) {
|
||||
idx := bytes.Index(blob, localEncryptorPrefix)
|
||||
if idx < 0 {
|
||||
return nil, errYandexMarkerNotFound
|
||||
}
|
||||
payload := blob[idx+len(localEncryptorPrefix):]
|
||||
if len(payload) < yandexIntKeyBlobLen {
|
||||
return nil, errYandexBlobShort
|
||||
}
|
||||
|
||||
plaintext, err := AESGCMDecryptBlob(masterKey, payload[:yandexIntKeyBlobLen], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !bytes.HasPrefix(plaintext, yandexSignature) {
|
||||
return nil, errYandexBadSignature
|
||||
}
|
||||
plaintext = plaintext[len(yandexSignature):]
|
||||
if len(plaintext) < yandexDataKeyLen {
|
||||
return nil, errYandexKeyTooShort
|
||||
}
|
||||
return plaintext[:yandexDataKeyLen], nil
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// encryptAESGCM is a test helper that produces a GCM ciphertext with caller-supplied AAD.
|
||||
func encryptAESGCM(t *testing.T, key, nonce, plaintext, aad []byte) []byte {
|
||||
t.Helper()
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
t.Fatalf("aes.NewCipher: %v", err)
|
||||
}
|
||||
aead, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
t.Fatalf("cipher.NewGCM: %v", err)
|
||||
}
|
||||
return aead.Seal(nil, nonce, plaintext, aad)
|
||||
}
|
||||
|
||||
// testPlaintextPayloadLen: plaintext size before AES-GCM seal inside meta.local_encryptor_data.
|
||||
// 96 (blob) - 12 (nonce) - 16 (tag) = 68 bytes.
|
||||
const testPlaintextPayloadLen = yandexIntKeyBlobLen - gcmNonceSize - 16
|
||||
|
||||
func buildLocalEncryptorBlob(t *testing.T, masterKey, dataKey []byte) []byte {
|
||||
t.Helper()
|
||||
nonce := bytes.Repeat([]byte{0xAB}, gcmNonceSize)
|
||||
plaintext := append([]byte{}, yandexSignature...)
|
||||
plaintext = append(plaintext, dataKey...)
|
||||
plaintext = append(plaintext, make([]byte, testPlaintextPayloadLen-len(plaintext))...)
|
||||
ciphertext := encryptAESGCM(t, masterKey, nonce, plaintext, nil)
|
||||
if len(ciphertext) != yandexIntKeyBlobLen-gcmNonceSize {
|
||||
t.Fatalf("unexpected ciphertext len: got %d want %d", len(ciphertext), yandexIntKeyBlobLen-gcmNonceSize)
|
||||
}
|
||||
blob := []byte{0x01, 0x02, 0x03, 0x04} // arbitrary protobuf preamble
|
||||
blob = append(blob, localEncryptorPrefix...)
|
||||
blob = append(blob, nonce...)
|
||||
blob = append(blob, ciphertext...)
|
||||
blob = append(blob, 0xFF, 0xFE) // trailing junk should be ignored
|
||||
return blob
|
||||
}
|
||||
|
||||
func TestDecryptYandexIntermediateKey_RoundTrip(t *testing.T) {
|
||||
masterKey := bytes.Repeat([]byte{0x11}, 32)
|
||||
dataKey := bytes.Repeat([]byte{0x22}, yandexDataKeyLen)
|
||||
blob := buildLocalEncryptorBlob(t, masterKey, dataKey)
|
||||
|
||||
got, err := DecryptYandexIntermediateKey(masterKey, blob)
|
||||
if err != nil {
|
||||
t.Fatalf("DecryptYandexIntermediateKey: %v", err)
|
||||
}
|
||||
if !bytes.Equal(got, dataKey) {
|
||||
t.Errorf("key mismatch: got %x want %x", got, dataKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptYandexIntermediateKey_MissingMarker(t *testing.T) {
|
||||
_, err := DecryptYandexIntermediateKey(bytes.Repeat([]byte{0x11}, 32), []byte("no marker here"))
|
||||
if !errors.Is(err, errYandexMarkerNotFound) {
|
||||
t.Fatalf("expected errYandexMarkerNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptYandexIntermediateKey_Truncated(t *testing.T) {
|
||||
blob := append([]byte{0x00, 0x00}, localEncryptorPrefix...)
|
||||
blob = append(blob, bytes.Repeat([]byte{0x55}, yandexIntKeyBlobLen-1)...)
|
||||
_, err := DecryptYandexIntermediateKey(bytes.Repeat([]byte{0x11}, 32), blob)
|
||||
if !errors.Is(err, errYandexBlobShort) {
|
||||
t.Fatalf("expected errYandexBlobShort, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptYandexIntermediateKey_BadSignature(t *testing.T) {
|
||||
masterKey := bytes.Repeat([]byte{0x11}, 32)
|
||||
nonce := bytes.Repeat([]byte{0xAB}, gcmNonceSize)
|
||||
plaintext := append([]byte{0xDE, 0xAD, 0xBE, 0xEF}, bytes.Repeat([]byte{0x22}, yandexDataKeyLen)...)
|
||||
plaintext = append(plaintext, make([]byte, testPlaintextPayloadLen-len(plaintext))...)
|
||||
ciphertext := encryptAESGCM(t, masterKey, nonce, plaintext, nil)
|
||||
blob := append([]byte{}, localEncryptorPrefix...)
|
||||
blob = append(blob, nonce...)
|
||||
blob = append(blob, ciphertext...)
|
||||
|
||||
_, err := DecryptYandexIntermediateKey(masterKey, blob)
|
||||
if !errors.Is(err, errYandexBadSignature) {
|
||||
t.Fatalf("expected errYandexBadSignature, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDecryptYandexIntermediateKey_TrailingDataIgnored verifies that trailing bytes past
|
||||
// signature+32 are discarded.
|
||||
func TestDecryptYandexIntermediateKey_TrailingDataIgnored(t *testing.T) {
|
||||
masterKey := bytes.Repeat([]byte{0x11}, 32)
|
||||
nonce := bytes.Repeat([]byte{0xAB}, gcmNonceSize)
|
||||
plaintext := append([]byte{}, yandexSignature...)
|
||||
plaintext = append(plaintext, bytes.Repeat([]byte{0x22}, 16)...)
|
||||
plaintext = append(plaintext, make([]byte, testPlaintextPayloadLen-len(plaintext))...)
|
||||
ciphertext := encryptAESGCM(t, masterKey, nonce, plaintext, nil)
|
||||
blob := append([]byte{}, localEncryptorPrefix...)
|
||||
blob = append(blob, nonce...)
|
||||
blob = append(blob, ciphertext...)
|
||||
|
||||
got, err := DecryptYandexIntermediateKey(masterKey, blob)
|
||||
if err != nil {
|
||||
t.Fatalf("DecryptYandexIntermediateKey: %v", err)
|
||||
}
|
||||
want := bytes.Repeat([]byte{0x22}, 16)
|
||||
want = append(want, make([]byte, 16)...)
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("key mismatch: got %x want %x", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESGCMDecryptBlob_RoundTrip(t *testing.T) {
|
||||
key := bytes.Repeat([]byte{0x55}, 32)
|
||||
nonce := bytes.Repeat([]byte{0x66}, gcmNonceSize)
|
||||
aad := []byte("row-aad")
|
||||
plaintext := []byte("row-plaintext")
|
||||
blob := append([]byte{}, nonce...)
|
||||
blob = append(blob, encryptAESGCM(t, key, nonce, plaintext, aad)...)
|
||||
|
||||
got, err := AESGCMDecryptBlob(key, blob, aad)
|
||||
if err != nil {
|
||||
t.Fatalf("AESGCMDecryptBlob: %v", err)
|
||||
}
|
||||
if !bytes.Equal(got, plaintext) {
|
||||
t.Errorf("plaintext mismatch: got %q want %q", got, plaintext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESGCMDecryptBlob_BadAAD(t *testing.T) {
|
||||
key := bytes.Repeat([]byte{0x55}, 32)
|
||||
nonce := bytes.Repeat([]byte{0x66}, gcmNonceSize)
|
||||
blob := append([]byte{}, nonce...)
|
||||
blob = append(blob, encryptAESGCM(t, key, nonce, []byte("x"), []byte("aad-A"))...)
|
||||
|
||||
if _, err := AESGCMDecryptBlob(key, blob, []byte("aad-B")); err == nil {
|
||||
t.Fatal("expected authentication failure with mismatched AAD")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESGCMDecryptBlob_TooShort(t *testing.T) {
|
||||
_, err := AESGCMDecryptBlob(bytes.Repeat([]byte{0x55}, 32), []byte{0x01, 0x02}, nil)
|
||||
if !errors.Is(err, errShortCiphertext) {
|
||||
t.Fatalf("expected errShortCiphertext, got %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user