Skip to content

Commit

Permalink
feat(helper): Add function to quick check decryption with a session key
Browse files Browse the repository at this point in the history
This commit adds a function to the helper package that allows to check with high probability
if a sessionkey can decrypt a data packet given its 24 byte prefix.
  • Loading branch information
lubux committed Aug 24, 2023
1 parent b97d994 commit 589a092
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 0 deletions.
95 changes: 95 additions & 0 deletions helper/decrypt_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package helper

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"io"

"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/pkg/errors"
)

const AES_BLOCK_SIZE = 16

func supported(cipher packet.CipherFunction) bool {
switch cipher {

Check failure on line 17 in helper/decrypt_check.go

View workflow job for this annotation

GitHub Actions / Lint

missing cases in switch of type packet.CipherFunction: Cipher3DES, CipherCAST5 (exhaustive)
case packet.CipherAES128, packet.CipherAES192, packet.CipherAES256:
return true
}
return false
}

func blockSize(cipher packet.CipherFunction) int {
switch cipher {

Check failure on line 25 in helper/decrypt_check.go

View workflow job for this annotation

GitHub Actions / Lint

missing cases in switch of type packet.CipherFunction: Cipher3DES, CipherCAST5 (exhaustive)
case packet.CipherAES128, packet.CipherAES192, packet.CipherAES256:
return AES_BLOCK_SIZE
}
return 0
}

func blockCipher(cipher packet.CipherFunction, key []byte) (cipher.Block, error) {
switch cipher {

Check failure on line 33 in helper/decrypt_check.go

View workflow job for this annotation

GitHub Actions / Lint

missing cases in switch of type packet.CipherFunction: Cipher3DES, CipherCAST5 (exhaustive)
case packet.CipherAES128, packet.CipherAES192, packet.CipherAES256:
return aes.NewCipher(key)
}
return nil, errors.New("gopenpgp: unsupported ciphers")
}

// QuickCheckDecryptReader checks with high probability if the provided session key
// can decrypt a data packet given its 24 byte long prefix.
// The method reads up to but not exactly 24 bytes from the prefixReader.
// NOTE: Only works for SEIPDv1 packets with AES.
func QuickCheckDecryptReader(sessionKey *crypto.SessionKey, prefixReader crypto.Reader) (bool, error) {
algo, err := sessionKey.GetCipherFunc()
if err != nil {
return false, errors.New("gopenpgp: cipher algorithm not found")
}
if !supported(algo) {
return false, errors.New("gopenpgp: cipher not supported for fast check")
}
packetParser := packet.NewReader(prefixReader)
_, err = packetParser.Next()
if err != nil {
return false, errors.New("gopenpgp: failed to parse packet prefix")
}

blockSize := blockSize(algo)
encryptedData := make([]byte, blockSize+2)
_, err = io.ReadFull(prefixReader, encryptedData)
if err != nil {
return false, errors.New("gopenpgp: prefix is too short to check")
}

blockCipher, err := blockCipher(algo, sessionKey.Key)
if err != nil {
return false, errors.New("gopenpgp: failed to initialize the cipher")
}

blockBuffer := make([]byte, blockSize)
// Decrypt 2 bytes of the second block
blockCipher.Encrypt(blockBuffer[:], encryptedData[:blockSize])

Check failure on line 72 in helper/decrypt_check.go

View workflow job for this annotation

GitHub Actions / Lint

unslice: could simplify blockBuffer[:] to blockBuffer (gocritic)
for ind := range blockBuffer[:2] {
encryptedData[blockSize+ind] ^= blockBuffer[ind]
}
for ind := range blockBuffer[:] {

Check failure on line 76 in helper/decrypt_check.go

View workflow job for this annotation

GitHub Actions / Lint

unslice: could simplify blockBuffer[:] to blockBuffer (gocritic)
blockBuffer[ind] = 0
}
// Decrypt the first block
blockCipher.Encrypt(blockBuffer[:], blockBuffer[:])

Check failure on line 80 in helper/decrypt_check.go

View workflow job for this annotation

GitHub Actions / Lint

unslice: could simplify blockBuffer[:] to blockBuffer (gocritic)
for ind := range blockBuffer[:] {
encryptedData[ind] ^= blockBuffer[ind]
}

return encryptedData[blockSize-2] == encryptedData[blockSize] &&
encryptedData[blockSize-1] == encryptedData[blockSize+1], nil
}

// QuickCheckDecrypt checks with high probability if the provided session key
// can decrypt the data packet given its 24 byte long prefix.
// The method only considers the first 24 bytes of the prefixDataPacket slice (prefixDataPacket[:24]).
// NOTE: Only works for SEIPDv1 packets with AES.
func QuickCheckDecrypt(sessionKey *crypto.SessionKey, prefixDataPacket []byte) (bool, error) {
return QuickCheckDecryptReader(sessionKey, bytes.NewReader(prefixDataPacket))
}
43 changes: 43 additions & 0 deletions helper/decrypt_check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package helper

import (
"encoding/hex"
"testing"

"github.com/ProtonMail/gopenpgp/v2/crypto"
)

const testQuickCheckSessionKey = `038c9cb9d408074e36bac22c6b90973082f86e5b01f38b787da3927000365a81`
const testQuickCheckSessionKeyAlg = "aes256"
const testQuickCheckDataPacket = `d2540152ab2518950f282d98d901eb93c00fb55a3bb30b3b517d6a356f57884bac6963060ebb167ffc3296e5e99ec058aeff5003a4784a0734a62861ae56d2921b9b790d50586cd21cad45e2d84ac93fb5d8af2ce6c5`

func TestCheckDecrypt(t *testing.T) {
sessionKeyData, err := hex.DecodeString(testQuickCheckSessionKey)
if err != nil {
t.Error(err)
}
dataPacket, err := hex.DecodeString(testQuickCheckDataPacket)
if err != nil {
t.Error(err)
}
sessionKey := &crypto.SessionKey{
Key: sessionKeyData,
Algo: testQuickCheckSessionKeyAlg,
}
ok, err := QuickCheckDecrypt(sessionKey, dataPacket[:22])
if err != nil {
t.Error(err)
}
if !ok {
t.Error("should be able to decrypt")
}

sessionKey.Key[0] += 1
ok, err = QuickCheckDecrypt(sessionKey, dataPacket[:22])
if err != nil {
t.Error(err)
}
if ok {
t.Error("should no be able to decrypt")
}
}

0 comments on commit 589a092

Please sign in to comment.