Skip to content

Commit 987923f

Browse files
authored
Add quick check for session key decryption (#249)
This commit adds the function QuickCheckDecrypt to the helper package. The function allows to check with high probability if a session key can decrypt a data packet given its 24-byte prefix. It only works for SEIPDv1 data packets that uses AES as a cipher.
1 parent b97d994 commit 987923f

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

helper/decrypt_check.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package helper
2+
3+
import (
4+
"bytes"
5+
"crypto/aes"
6+
"crypto/cipher"
7+
"io"
8+
9+
"github.com/ProtonMail/go-crypto/openpgp/packet"
10+
"github.com/ProtonMail/gopenpgp/v2/crypto"
11+
"github.com/pkg/errors"
12+
)
13+
14+
const AES_BLOCK_SIZE = 16
15+
16+
func supported(cipher packet.CipherFunction) bool {
17+
switch cipher {
18+
case packet.CipherAES128, packet.CipherAES192, packet.CipherAES256:
19+
return true
20+
case packet.CipherCAST5, packet.Cipher3DES:
21+
return false
22+
}
23+
return false
24+
}
25+
26+
func blockSize(cipher packet.CipherFunction) int {
27+
switch cipher {
28+
case packet.CipherAES128, packet.CipherAES192, packet.CipherAES256:
29+
return AES_BLOCK_SIZE
30+
case packet.CipherCAST5, packet.Cipher3DES:
31+
return 0
32+
}
33+
return 0
34+
}
35+
36+
func blockCipher(cipher packet.CipherFunction, key []byte) (cipher.Block, error) {
37+
switch cipher {
38+
case packet.CipherAES128, packet.CipherAES192, packet.CipherAES256:
39+
return aes.NewCipher(key)
40+
case packet.CipherCAST5, packet.Cipher3DES:
41+
return nil, errors.New("gopenpgp: cipher not supported for quick check")
42+
}
43+
return nil, errors.New("gopenpgp: unknown cipher")
44+
}
45+
46+
// QuickCheckDecryptReader checks with high probability if the provided session key
47+
// can decrypt a data packet given its 24 byte long prefix.
48+
// The method reads up to but not exactly 24 bytes from the prefixReader.
49+
// NOTE: Only works for SEIPDv1 packets with AES.
50+
func QuickCheckDecryptReader(sessionKey *crypto.SessionKey, prefixReader crypto.Reader) (bool, error) {
51+
algo, err := sessionKey.GetCipherFunc()
52+
if err != nil {
53+
return false, errors.New("gopenpgp: cipher algorithm not found")
54+
}
55+
if !supported(algo) {
56+
return false, errors.New("gopenpgp: cipher not supported for quick check")
57+
}
58+
packetParser := packet.NewReader(prefixReader)
59+
_, err = packetParser.Next()
60+
if err != nil {
61+
return false, errors.New("gopenpgp: failed to parse packet prefix")
62+
}
63+
64+
blockSize := blockSize(algo)
65+
encryptedData := make([]byte, blockSize+2)
66+
_, err = io.ReadFull(prefixReader, encryptedData)
67+
if err != nil {
68+
return false, errors.New("gopenpgp: prefix is too short to check")
69+
}
70+
71+
blockCipher, err := blockCipher(algo, sessionKey.Key)
72+
if err != nil {
73+
return false, errors.New("gopenpgp: failed to initialize the cipher")
74+
}
75+
_ = packet.NewOCFBDecrypter(blockCipher, encryptedData, packet.OCFBNoResync)
76+
return encryptedData[blockSize-2] == encryptedData[blockSize] &&
77+
encryptedData[blockSize-1] == encryptedData[blockSize+1], nil
78+
}
79+
80+
// QuickCheckDecrypt checks with high probability if the provided session key
81+
// can decrypt the encrypted data packet given its 24 byte long prefix.
82+
// The method only considers the first 24 bytes of the prefix slice (prefix[:24]).
83+
// NOTE: Only works for SEIPDv1 packets with AES.
84+
func QuickCheckDecrypt(sessionKey *crypto.SessionKey, prefix []byte) (bool, error) {
85+
return QuickCheckDecryptReader(sessionKey, bytes.NewReader(prefix))
86+
}

helper/decrypt_check_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package helper
2+
3+
import (
4+
"encoding/hex"
5+
"testing"
6+
7+
"github.com/ProtonMail/gopenpgp/v2/crypto"
8+
)
9+
10+
const testQuickCheckSessionKey = `038c9cb9d408074e36bac22c6b90973082f86e5b01f38b787da3927000365a81`
11+
const testQuickCheckSessionKeyAlg = "aes256"
12+
const testQuickCheckDataPacket = `d2540152ab2518950f282d98d901eb93c00fb55a3bb30b3b517d6a356f57884bac6963060ebb167ffc3296e5e99ec058aeff5003a4784a0734a62861ae56d2921b9b790d50586cd21cad45e2d84ac93fb5d8af2ce6c5`
13+
14+
func TestCheckDecrypt(t *testing.T) {
15+
sessionKeyData, err := hex.DecodeString(testQuickCheckSessionKey)
16+
if err != nil {
17+
t.Error(err)
18+
}
19+
dataPacket, err := hex.DecodeString(testQuickCheckDataPacket)
20+
if err != nil {
21+
t.Error(err)
22+
}
23+
sessionKey := &crypto.SessionKey{
24+
Key: sessionKeyData,
25+
Algo: testQuickCheckSessionKeyAlg,
26+
}
27+
ok, err := QuickCheckDecrypt(sessionKey, dataPacket[:22])
28+
if err != nil {
29+
t.Error(err)
30+
}
31+
if !ok {
32+
t.Error("should be able to decrypt")
33+
}
34+
35+
sessionKey.Key[0] += 1
36+
ok, err = QuickCheckDecrypt(sessionKey, dataPacket[:22])
37+
if err != nil {
38+
t.Error(err)
39+
}
40+
if ok {
41+
t.Error("should no be able to decrypt")
42+
}
43+
}

0 commit comments

Comments
 (0)