-
Notifications
You must be signed in to change notification settings - Fork 117
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
2 changed files
with
129 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |