Skip to content

Commit

Permalink
feat: Refactor crypto decryption functions for consistency and error …
Browse files Browse the repository at this point in the history
…handling (#302)

* feat: Refactor crypto decryption functions for consistency and error handling

- Close #301
- Refactored and renamed decryption functions across multiple files for consistency
- Updated cookie sorting method to sort in descending order
- Added new encryption functions for AES in CBC and GCM modes and DES in CBC mode
- Added error handling to decryption functions and created new error variables for invalid ciphertext length and decode failures
- Test cases added for encryption and decryption functions
- Removed unused code and imports.

* chore: Add new words to .typos.toml dictionary

- Add new terms to `.typos.toml` dictionary
- Improve code formatting and readability
- Refactor functions for better performance
- Update comments and documentation
- Resolve minor bugs and errors

* refactor: Refactor crypto package for better structure and readability

- Refactored and cleaned up crypto package code for better readability
- Renamed `ToByteArray` method to `bytes` for consistency
- Modified `DecryptWithDPAPI` method to use `outBlob.bytes()` for efficiency
- Added comments and removed unused methods in `loginPBE`
- Refactored `nssPBE` and `metaPBE` Decrypt methods to use `deriveKeyAndIV` helper method
- Improved overall maintainability and organization of codebase

* refactor: Refactor firefox password encryption and decryption.

- Implement ASN1PBE interface with various PBE struct types and encryption/decryption methods
- Fix naming and remove unused variables in browsingdata and crypto files
- Add tests for ASN1PBE implementation using external assertion package
- Refactor and improve error handling in firefox file functions related to master key retrieval
- Add input validation and AES-GCM encryption function to crypto file
  • Loading branch information
moonD4rk committed Jan 27, 2024
1 parent c150b22 commit 591b97c
Show file tree
Hide file tree
Showing 13 changed files with 750 additions and 278 deletions.
2 changes: 2 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
Readed = "Readed"
Sie = "Sie"
OT = "OT"
Encrypter = "Encrypter"
Decrypter = "Decrypter"
[files]
extend-exclude = ["go.mod", "go.sum"]
2 changes: 1 addition & 1 deletion browser/chromium/chromium_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (c *Chromium) GetMasterKey() ([]byte, error) {
if err != nil {
return nil, errDecodeMasterKeyFailed
}
c.masterKey, err = crypto.DPAPI(key[5:])
c.masterKey, err = crypto.DecryptWithDPAPI(key[5:])
if err != nil {
slog.Error("decrypt master key failed", "err", err)
return nil, err
Expand Down
45 changes: 24 additions & 21 deletions browser/firefox/firefox.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (f *Firefox) GetMasterKey() ([]byte, error) {
defer os.Remove(tempFilename)
defer keyDB.Close()

globalSalt, metaBytes, err := queryMetaData(keyDB)
metaItem1, metaItem2, err := queryMetaData(keyDB)
if err != nil {
return nil, fmt.Errorf("query metadata error: %w", err)
}
Expand All @@ -96,16 +96,16 @@ func (f *Firefox) GetMasterKey() ([]byte, error) {
return nil, fmt.Errorf("query NSS private error: %w", err)
}

return processMasterKey(globalSalt, metaBytes, nssA11, nssA102)
return processMasterKey(metaItem1, metaItem2, nssA11, nssA102)
}

func queryMetaData(db *sql.DB) ([]byte, []byte, error) {
const query = `SELECT item1, item2 FROM metaData WHERE id = 'password'`
var globalSalt, metaBytes []byte
if err := db.QueryRow(query).Scan(&globalSalt, &metaBytes); err != nil {
var metaItem1, metaItem2 []byte
if err := db.QueryRow(query).Scan(&metaItem1, &metaItem2); err != nil {
return nil, nil, err
}
return globalSalt, metaBytes, nil
return metaItem1, metaItem2, nil
}

func queryNssPrivate(db *sql.DB) ([]byte, []byte, error) {
Expand All @@ -119,37 +119,40 @@ func queryNssPrivate(db *sql.DB) ([]byte, []byte, error) {

// processMasterKey process master key of Firefox.
// Process the metaBytes and nssA11 with the corresponding cryptographic operations.
func processMasterKey(globalSalt, metaBytes, nssA11, nssA102 []byte) ([]byte, error) {
metaPBE, err := crypto.NewASN1PBE(metaBytes)
func processMasterKey(metaItem1, metaItem2, nssA11, nssA102 []byte) ([]byte, error) {
metaPBE, err := crypto.NewASN1PBE(metaItem2)
if err != nil {
return nil, err
return nil, fmt.Errorf("error creating ASN1PBE from metaItem2: %w", err)
}

k, err := metaPBE.Decrypt(globalSalt)
flag, err := metaPBE.Decrypt(metaItem1)
if err != nil {
return nil, err
return nil, fmt.Errorf("error decrypting master key: %w", err)
}
const passwordCheck = "password-check"

if !bytes.Contains(k, []byte("password-check")) {
return nil, errors.New("password-check not found")
if !bytes.Contains(flag, []byte(passwordCheck)) {
return nil, errors.New("flag verification failed: password-check not found")
}
keyLin := []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}

var keyLin = []byte{248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
if !bytes.Equal(nssA102, keyLin) {
return nil, errors.New("nssA102 not equal keyLin")
return nil, errors.New("master key verification failed: nssA102 not equal to expected value")
}
nssPBE, err := crypto.NewASN1PBE(nssA11)

nssA11PBE, err := crypto.NewASN1PBE(nssA11)
if err != nil {
return nil, err
return nil, fmt.Errorf("error creating ASN1PBE from nssA11: %w", err)
}
finallyKey, err := nssPBE.Decrypt(globalSalt)

finallyKey, err := nssA11PBE.Decrypt(metaItem1)
if err != nil {
return nil, err
return nil, fmt.Errorf("error decrypting final key: %w", err)
}
if len(finallyKey) < 24 {
return nil, errors.New("finallyKey length less than 24")
return nil, errors.New("length of final key is less than 24 bytes")
}
finallyKey = finallyKey[:24]
return finallyKey, nil
return finallyKey[:24], nil
}

func (f *Firefox) Name() string {
Expand Down
4 changes: 2 additions & 2 deletions browsingdata/cookie/cookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ func (c *ChromiumCookie) Parse(masterKey []byte) error {
}
if len(encryptValue) > 0 {
if len(masterKey) == 0 {
value, err = crypto.DPAPI(encryptValue)
value, err = crypto.DecryptWithDPAPI(encryptValue)
} else {
value, err = crypto.DecryptPass(masterKey, encryptValue)
value, err = crypto.DecryptWithChromium(masterKey, encryptValue)
}
if err != nil {
slog.Error("decrypt chromium cookie error", "err", err)
Expand Down
8 changes: 4 additions & 4 deletions browsingdata/creditcard/creditcard.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ func (c *ChromiumCreditCard) Parse(masterKey []byte) error {
}
if len(encryptValue) > 0 {
if len(masterKey) == 0 {
value, err = crypto.DPAPI(encryptValue)
value, err = crypto.DecryptWithDPAPI(encryptValue)
} else {
value, err = crypto.DecryptPass(masterKey, encryptValue)
value, err = crypto.DecryptWithChromium(masterKey, encryptValue)
}
if err != nil {
slog.Error("decrypt chromium credit card error", "err", err)
Expand Down Expand Up @@ -114,9 +114,9 @@ func (c *YandexCreditCard) Parse(masterKey []byte) error {
}
if len(encryptValue) > 0 {
if len(masterKey) == 0 {
value, err = crypto.DPAPI(encryptValue)
value, err = crypto.DecryptWithDPAPI(encryptValue)
} else {
value, err = crypto.DecryptPass(masterKey, encryptValue)
value, err = crypto.DecryptWithChromium(masterKey, encryptValue)
}
if err != nil {
slog.Error("decrypt chromium credit card error", "err", err)
Expand Down
19 changes: 7 additions & 12 deletions browsingdata/password/password.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ func (c *ChromiumPassword) Parse(masterKey []byte) error {
}
if len(pwd) > 0 {
if len(masterKey) == 0 {
password, err = crypto.DPAPI(pwd)
password, err = crypto.DecryptWithDPAPI(pwd)
} else {
password, err = crypto.DecryptPass(masterKey, pwd)
password, err = crypto.DecryptWithChromium(masterKey, pwd)
}
if err != nil {
slog.Error("decrypt chromium password error", "err", err)
Expand Down Expand Up @@ -129,9 +129,9 @@ func (c *YandexPassword) Parse(masterKey []byte) error {

if len(pwd) > 0 {
if len(masterKey) == 0 {
password, err = crypto.DPAPI(pwd)
password, err = crypto.DecryptWithDPAPI(pwd)
} else {
password, err = crypto.DecryptPass(masterKey, pwd)
password, err = crypto.DecryptWithChromium(masterKey, pwd)
}
if err != nil {
slog.Error("decrypt yandex password error", "err", err)
Expand Down Expand Up @@ -162,12 +162,7 @@ func (c *YandexPassword) Len() int {

type FirefoxPassword []loginData

const (
queryMetaData = `SELECT item1, item2 FROM metaData WHERE id = 'password'`
queryNssPrivate = `SELECT a11, a102 from nssPrivate`
)

func (f *FirefoxPassword) Parse(masterKey []byte) error {
func (f *FirefoxPassword) Parse(globalSalt []byte) error {
logins, err := getFirefoxLoginData()
if err != nil {
return err
Expand All @@ -182,11 +177,11 @@ func (f *FirefoxPassword) Parse(masterKey []byte) error {
if err != nil {
return err
}
user, err := userPBE.Decrypt(masterKey)
user, err := userPBE.Decrypt(globalSalt)
if err != nil {
return err
}
pwd, err := pwdPBE.Decrypt(masterKey)
pwd, err := pwdPBE.Decrypt(globalSalt)
if err != nil {
return err
}
Expand Down
194 changes: 194 additions & 0 deletions crypto/asn1pbe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package crypto

import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/asn1"
"errors"

"golang.org/x/crypto/pbkdf2"
)

type ASN1PBE interface {
Decrypt(globalSalt []byte) ([]byte, error)

Encrypt(globalSalt, plaintext []byte) ([]byte, error)
}

func NewASN1PBE(b []byte) (pbe ASN1PBE, err error) {
var (
nss nssPBE
meta metaPBE
login loginPBE
)
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, ErrDecodeASN1Failed
}

var ErrDecodeASN1Failed = errors.New("decode ASN1 data failed")

// nssPBE Struct
//
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// SEQUENCE (2 elem)
// OCTET STRING (20 byte)
// INTEGER 1
// OCTET STRING (16 byte)
type nssPBE struct {
AlgoAttr struct {
asn1.ObjectIdentifier
SaltAttr struct {
EntrySalt []byte
Len int
}
}
Encrypted []byte
}

// Decrypt decrypts the encrypted password with the global salt.
func (n nssPBE) Decrypt(globalSalt []byte) ([]byte, error) {
key, iv := n.deriveKeyAndIV(globalSalt)

return DES3Decrypt(key, iv, n.Encrypted)
}

func (n nssPBE) Encrypt(globalSalt []byte, plaintext []byte) ([]byte, error) {
key, iv := n.deriveKeyAndIV(globalSalt)

return DES3Encrypt(key, iv, plaintext)
}

// deriveKeyAndIV derives the key and initialization vector (IV)
// from the global salt and entry salt.
func (n nssPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
salt := n.AlgoAttr.SaltAttr.EntrySalt
hashPrefix := sha1.Sum(globalSalt)
compositeHash := sha1.Sum(append(hashPrefix[:], salt...))
paddedEntrySalt := paddingZero(salt, 20)

hmacProcessor := hmac.New(sha1.New, compositeHash[:])
hmacProcessor.Write(paddedEntrySalt)

paddedEntrySalt = append(paddedEntrySalt, salt...)
keyComponent1 := hmac.New(sha1.New, compositeHash[:])
keyComponent1.Write(paddedEntrySalt)

hmacWithSalt := append(hmacProcessor.Sum(nil), salt...)
keyComponent2 := hmac.New(sha1.New, compositeHash[:])
keyComponent2.Write(hmacWithSalt)

key := append(keyComponent1.Sum(nil), keyComponent2.Sum(nil)...)
iv := key[len(key)-8:]
return key[:24], iv
}

// 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 metaPBE struct {
AlgoAttr algoAttr
Encrypted []byte
}

type algoAttr struct {
asn1.ObjectIdentifier
Data struct {
Data struct {
asn1.ObjectIdentifier
SlatAttr slatAttr
}
IVData ivAttr
}
}

type ivAttr struct {
asn1.ObjectIdentifier
IV []byte
}

type slatAttr struct {
EntrySalt []byte
IterationCount int
KeySize int
Algorithm struct {
asn1.ObjectIdentifier
}
}

func (m metaPBE) Decrypt(globalSalt []byte) ([]byte, error) {
key, iv := m.deriveKeyAndIV(globalSalt)

return AES128CBCDecrypt(key, iv, m.Encrypted)
}

func (m metaPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
key, iv := m.deriveKeyAndIV(globalSalt)

return AES128CBCEncrypt(key, iv, plaintext)
}

func (m metaPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
password := sha1.Sum(globalSalt)

salt := m.AlgoAttr.Data.Data.SlatAttr.EntrySalt
iter := m.AlgoAttr.Data.Data.SlatAttr.IterationCount
keyLen := m.AlgoAttr.Data.Data.SlatAttr.KeySize

key := pbkdf2.Key(password[:], salt, iter, keyLen, sha256.New)
iv := append([]byte{4, 14}, m.AlgoAttr.Data.IVData.IV...)
return key, iv
}

// loginPBE Struct
//
// OCTET STRING (16 byte)
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// OCTET STRING (8 byte)
// OCTET STRING (16 byte)
type loginPBE struct {
CipherText []byte
Data struct {
asn1.ObjectIdentifier
IV []byte
}
Encrypted []byte
}

func (l loginPBE) Decrypt(globalSalt []byte) ([]byte, error) {
key, iv := l.deriveKeyAndIV(globalSalt)
return DES3Decrypt(key, iv, l.Encrypted)
}

func (l loginPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) {
key, iv := l.deriveKeyAndIV(globalSalt)
return DES3Encrypt(key, iv, plaintext)
}

func (l loginPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) {
return globalSalt, l.Data.IV
}
Loading

0 comments on commit 591b97c

Please sign in to comment.