Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New function to quick check if a session key can decrypt a encrypted data packet #249

Merged
merged 3 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions helper/decrypt_check.go
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))
}
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")
}
}
Loading