Files
HackBrowserData/crypto/asn1pbe.go
T
Roger 410bffe643 refactor: naming cleanup and crypto package improvements (#551)
* refactor: naming cleanup across all packages
2026-04-05 16:51:56 +08:00

214 lines
5.3 KiB
Go

package crypto
import (
"crypto/aes"
"crypto/des"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/asn1"
)
const des3KeySize = 24 // 3DES uses 24-byte (192-bit) keys
// ASN1PBE represents a Password-Based Encryption structure from Firefox's NSS.
// The key parameter semantics vary by implementation:
// - privateKeyPBE / passwordCheckPBE: key is the global salt used for key derivation
// - credentialPBE: key is the already-derived master key
type ASN1PBE interface {
Decrypt(key []byte) ([]byte, error)
Encrypt(key, plaintext []byte) ([]byte, error)
}
func NewASN1PBE(b []byte) (pbe ASN1PBE, err error) {
var (
nss privateKeyPBE
meta passwordCheckPBE
login credentialPBE
)
if _, err := asn1.Unmarshal(b, &nss); err == nil {
return nss, nil
}
if _, err := asn1.Unmarshal(b, &meta); err == nil {
return meta, nil
}
if _, err := asn1.Unmarshal(b, &login); err == nil {
return login, nil
}
return nil, errDecodeASN1
}
// privateKeyPBE Struct
//
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// SEQUENCE (2 elem)
// OCTET STRING (20 byte)
// INTEGER 1
// OCTET STRING (16 byte)
type privateKeyPBE struct {
AlgoAttr struct {
asn1.ObjectIdentifier
SaltAttr struct {
EntrySalt []byte
KeyLen int
}
}
Encrypted []byte
}
func (n privateKeyPBE) Decrypt(globalSalt []byte) ([]byte, error) {
key, iv := n.deriveKeyAndIV(globalSalt)
return DES3Decrypt(key, iv, n.Encrypted)
}
func (n privateKeyPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
key, iv := n.deriveKeyAndIV(globalSalt)
return DES3Encrypt(key, iv, plaintext)
}
// deriveKeyAndIV implements NSS PBE-SHA1-3DES key derivation.
// Reference: https://searchfox.org/mozilla-central/source/security/nss/lib/softoken/lowpbe.c
//
// Derivation steps:
//
// hp = SHA1(globalSalt)
// ck = SHA1(hp || entrySalt)
// hmac1 = HMAC-SHA1(ck, paddedSalt)
// k1 = HMAC-SHA1(ck, paddedSalt || entrySalt)
// k2 = HMAC-SHA1(ck, hmac1 || entrySalt)
// dk = k1 || k2 (40 bytes)
// key = dk[:24], iv = dk[32:]
func (n privateKeyPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
entrySalt := n.AlgoAttr.SaltAttr.EntrySalt
hp := sha1.Sum(globalSalt)
ck := sha1.Sum(append(hp[:], entrySalt...))
paddedSalt := paddingZero(entrySalt, 20)
hmac1 := hmac.New(sha1.New, ck[:])
hmac1.Write(paddedSalt)
k1 := hmac.New(sha1.New, ck[:])
k1.Write(append(paddedSalt, entrySalt...))
k2 := hmac.New(sha1.New, ck[:])
k2.Write(append(hmac1.Sum(nil), entrySalt...))
dk := append(k1.Sum(nil), k2.Sum(nil)...)
return dk[:24], dk[len(dk)-8:]
}
// MetaPBE Struct
//
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// SEQUENCE (2 elem)
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// SEQUENCE (4 elem)
// OCTET STRING (32 byte)
// INTEGER 1
// INTEGER 32
// SEQUENCE (1 elem)
// OBJECT IDENTIFIER
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// OCTET STRING (14 byte)
// OCTET STRING (16 byte)
type passwordCheckPBE struct {
AlgoAttr algoAttr
Encrypted []byte
}
type algoAttr struct {
asn1.ObjectIdentifier
KDFParams struct {
PBKDF2 struct {
asn1.ObjectIdentifier
SaltAttr saltAttr
}
IVData ivAttr
}
}
type ivAttr struct {
asn1.ObjectIdentifier
IV []byte
}
type saltAttr struct {
EntrySalt []byte
IterationCount int
KeySize int
Algorithm struct {
asn1.ObjectIdentifier
}
}
func (m passwordCheckPBE) Decrypt(globalSalt []byte) ([]byte, error) {
key, iv := m.deriveKeyAndIV(globalSalt)
return AESCBCDecrypt(key, iv, m.Encrypted)
}
func (m passwordCheckPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
key, iv := m.deriveKeyAndIV(globalSalt)
return AESCBCEncrypt(key, iv, plaintext)
}
func (m passwordCheckPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
password := sha1.Sum(globalSalt)
params := m.AlgoAttr.KDFParams.PBKDF2.SaltAttr
key := PBKDF2Key(password[:], params.EntrySalt, params.IterationCount, params.KeySize, sha256.New)
// Firefox stores the IV with its ASN.1 OCTET STRING header (tag=0x04, length=0x0E).
// The full 16-byte IV = [0x04, 0x0E] + 14-byte IV value from the parsed structure.
iv := append([]byte{0x04, 0x0E}, m.AlgoAttr.KDFParams.IVData.IV...)
return key, iv
}
// credentialPBE Struct
//
// OCTET STRING (16 byte)
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// OCTET STRING (8 byte)
// OCTET STRING (16 byte)
type credentialPBE struct {
KeyCheck []byte
Algo struct {
asn1.ObjectIdentifier
IV []byte
}
Encrypted []byte
}
func (l credentialPBE) Decrypt(masterKey []byte) ([]byte, error) {
key, iv := l.deriveKeyAndIV(masterKey)
// The cipher is inferred from IV length (avoids fragile OID checks):
switch len(iv) {
case des.BlockSize: // 8: 3DES-CBC (legacy Firefox)
return DES3Decrypt(key[:des3KeySize], iv, l.Encrypted)
case aes.BlockSize: // 16: AES-256-CBC (Firefox 144+)
return AESCBCDecrypt(key, iv, l.Encrypted)
default:
return nil, errUnsupportedIVLen
}
}
func (l credentialPBE) Encrypt(masterKey, plaintext []byte) ([]byte, error) {
key, iv := l.deriveKeyAndIV(masterKey)
switch len(iv) {
case des.BlockSize:
return DES3Encrypt(key[:des3KeySize], iv, plaintext)
case aes.BlockSize:
return AESCBCEncrypt(key, iv, plaintext)
default:
return nil, errUnsupportedIVLen
}
}
func (l credentialPBE) deriveKeyAndIV(masterKey []byte) ([]byte, []byte) {
return masterKey, l.Algo.IV
}