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.
This commit is contained in:
parent
b97d994962
commit
987923f9d8
2 changed files with 129 additions and 0 deletions
86
helper/decrypt_check.go
Normal file
86
helper/decrypt_check.go
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
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 {
|
||||||
|
case packet.CipherAES128, packet.CipherAES192, packet.CipherAES256:
|
||||||
|
return true
|
||||||
|
case packet.CipherCAST5, packet.Cipher3DES:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockSize(cipher packet.CipherFunction) int {
|
||||||
|
switch cipher {
|
||||||
|
case packet.CipherAES128, packet.CipherAES192, packet.CipherAES256:
|
||||||
|
return AES_BLOCK_SIZE
|
||||||
|
case packet.CipherCAST5, packet.Cipher3DES:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockCipher(cipher packet.CipherFunction, key []byte) (cipher.Block, error) {
|
||||||
|
switch cipher {
|
||||||
|
case packet.CipherAES128, packet.CipherAES192, packet.CipherAES256:
|
||||||
|
return aes.NewCipher(key)
|
||||||
|
case packet.CipherCAST5, packet.Cipher3DES:
|
||||||
|
return nil, errors.New("gopenpgp: cipher not supported for quick check")
|
||||||
|
}
|
||||||
|
return nil, errors.New("gopenpgp: unknown cipher")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 quick 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")
|
||||||
|
}
|
||||||
|
_ = packet.NewOCFBDecrypter(blockCipher, encryptedData, packet.OCFBNoResync)
|
||||||
|
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 encrypted data packet given its 24 byte long prefix.
|
||||||
|
// The method only considers the first 24 bytes of the prefix slice (prefix[:24]).
|
||||||
|
// NOTE: Only works for SEIPDv1 packets with AES.
|
||||||
|
func QuickCheckDecrypt(sessionKey *crypto.SessionKey, prefix []byte) (bool, error) {
|
||||||
|
return QuickCheckDecryptReader(sessionKey, bytes.NewReader(prefix))
|
||||||
|
}
|
||||||
43
helper/decrypt_check_test.go
Normal file
43
helper/decrypt_check_test.go
Normal file
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue