From 6610c92058a7209456985b8c43146fbe5b232d01 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Sun, 21 Jan 2024 15:11:22 -0500 Subject: [PATCH] add some KEK and CEK functions --- cek.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 2 ++ kek.go | 24 +++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 cek.go create mode 100644 kek.go diff --git a/cek.go b/cek.go new file mode 100644 index 0000000..b91e32a --- /dev/null +++ b/cek.go @@ -0,0 +1,94 @@ +package main + +import ( + "crypto/cipher" + "crypto/des" + "errors" + "fmt" +) + +// decryptCEK decrypts the encrypted CEK and unwraps the CEK so only the +// original CEK is returned +func decryptCEK(encryptedCEK, encryptedCekSalt, KEK []byte) (CEK []byte, err error) { + // ensure proper var lens, or error + encryptedCEKLen := 24 + CEKSaltLen := 8 + KEKLen := 24 + + if len(encryptedCEK) != encryptedCEKLen { + return nil, errors.New("wrong encrypted CEK length") + } + if len(encryptedCekSalt) != CEKSaltLen { + return nil, errors.New("wrong encrypted CEK's salt length") + } + if len(KEK) != KEKLen { + return nil, errors.New("wrong KEK length") + } + + // 3DES uses block byte size of 8 + blockByteSize := 8 + + // make DES cipher from KEK + kekDesCipher, err := des.NewTripleDESCipher(KEK) + if err != nil { + return nil, fmt.Errorf("failed to make DES cipher for cek decryption (%s)", err) + } + + // (1) first use n-1'th block as IV to decrypt n'th block + ivStart := encryptedCEKLen - 2*blockByteSize + ivEnd := encryptedCEKLen - 1*blockByteSize + + ivBlockCipherText := encryptedCEK[ivStart:ivEnd] + nthBlockCipherText := encryptedCEK[encryptedCEKLen-1*blockByteSize:] + + firstBlockDecrypter := cipher.NewCBCDecrypter(kekDesCipher, ivBlockCipherText) + + decryptedNthBlock := make([]byte, len(nthBlockCipherText)) + firstBlockDecrypter.CryptBlocks(decryptedNthBlock, nthBlockCipherText) + + // (2) decrypt remainder of outer encryption blocks (1 ... n-1'th) using + // the decrypted nthBlock as the IV + outerRemainderDecrypter := cipher.NewCBCDecrypter(kekDesCipher, decryptedNthBlock) + + decryptedOuterRemainder := make([]byte, encryptedCEKLen-1*blockByteSize) + outerRemainderDecrypter.CryptBlocks(decryptedOuterRemainder, encryptedCEK[:encryptedCEKLen-1*blockByteSize]) + + // combine decrypted remainder with decrypted nth block for complete decrypted bytes + // this is equivelant to having the outer encryption removed, AKA the CEK is encrypted + // once now instead of twice + onceEncryptedCEK := append(decryptedOuterRemainder, decryptedNthBlock...) + + // (3) Decrypted the inner layer of encryption using the KEK (aka decrypt the remaining + // layer of encryption) + + // inner decrypter uses original CEK salt + innerDecrypter := cipher.NewCBCDecrypter(kekDesCipher, encryptedCekSalt) + + // once decrypted, the CEK is still formatted as: + // CEK byte count || check value || CEK || padding (if required) + formattedCEK := make([]byte, len(onceEncryptedCEK)) + innerDecrypter.CryptBlocks(formattedCEK, onceEncryptedCEK) + + // Now that CEK is decrypted, sanity check it + + // first byte is CEK byte count + expectedCEKLen := formattedCEK[0] + + // (1a) expected cek len must be 16 or 24 or 3DES (which is what APC uses) + if int(expectedCEKLen) != 16 && int(expectedCEKLen) != 24 { + return nil, errors.New("expected CEK len block size is %d but 3DES requires 16 or 24 (decrypting likely failed)") + } + + // next 3 bytes are the check value + CEKCheckVal := formattedCEK[1:4] + + // CEK itself is the next bytes until CEK is the expected length + CEK = formattedCEK[4 : expectedCEKLen+4] + + // (1b) key check data validation + if !isBitwiseCompliment(CEKCheckVal, CEK[0:3]) { + return nil, errors.New("CEK check value did not match CEK") + } + + return CEK, nil +} diff --git a/go.mod b/go.mod index cdd2295..1870494 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module temp go 1.21 require github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 + +require golang.org/x/crypto v0.18.0 diff --git a/go.sum b/go.sum index a505f78..ccdf90c 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= diff --git a/kek.go b/kek.go new file mode 100644 index 0000000..fc29d9e --- /dev/null +++ b/kek.go @@ -0,0 +1,24 @@ +package main + +import ( + "crypto/sha256" + + "golang.org/x/crypto/pbkdf2" +) + +// makeKEK creates the APC KEK for a given Salt; APC uses a fixed +// password, iteration count, and hash function +func makeKEK(salt []byte) (KEK []byte) { + // password is known constant for APC files + password := "user" + + // fixed values for APC files + iterations := 5000 + hash := sha256.New + + // size of 3DES key (k1 + k2 + k3) + size := 24 + + // kek + return pbkdf2.Key([]byte(password), salt, iterations, size, hash) +}