@@ -4,14 +4,21 @@ import (
44 "bytes"
55 "crypto/aes"
66 "crypto/cipher"
7+ "crypto/sha256"
8+ "encoding/binary"
79 "io"
10+ "io/ioutil"
811
12+ "github.com/ProtonMail/go-crypto/eax"
13+ "github.com/ProtonMail/go-crypto/ocb"
914 "github.com/ProtonMail/go-crypto/openpgp/packet"
1015 "github.com/ProtonMail/gopenpgp/v2/crypto"
1116 "github.com/pkg/errors"
17+ "golang.org/x/crypto/hkdf"
1218)
1319
14- const AES_BLOCK_SIZE = 16
20+ const aesBlockSize = 16
21+ const copyChunkSize = 1024
1522
1623func supported (cipher packet.CipherFunction ) bool {
1724 switch cipher {
@@ -26,7 +33,7 @@ func supported(cipher packet.CipherFunction) bool {
2633func blockSize (cipher packet.CipherFunction ) int {
2734 switch cipher {
2835 case packet .CipherAES128 , packet .CipherAES192 , packet .CipherAES256 :
29- return AES_BLOCK_SIZE
36+ return aesBlockSize
3037 case packet .CipherCAST5 , packet .Cipher3DES :
3138 return 0
3239 }
@@ -43,44 +50,147 @@ func blockCipher(cipher packet.CipherFunction, key []byte) (cipher.Block, error)
4350 return nil , errors .New ("gopenpgp: unknown cipher" )
4451}
4552
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 ()
53+ func aeadMode (mode packet.AEADMode , block cipher.Block ) (alg cipher.AEAD , err error ) {
54+ switch mode {
55+ case packet .AEADModeEAX :
56+ alg , err = eax .NewEAX (block )
57+ case packet .AEADModeOCB :
58+ alg , err = ocb .NewOCB (block )
59+ case packet .AEADModeGCM :
60+ alg , err = cipher .NewGCM (block )
61+ }
5262 if err != nil {
53- return false , errors . New ( "gopenpgp: cipher algorithm not found" )
63+ return nil , err
5464 }
55- if ! supported (algo ) {
56- return false , errors .New ("gopenpgp: cipher not supported for quick check" )
65+ return
66+ }
67+
68+ func getSymmetricallyEncryptedAeadInstance (c packet.CipherFunction , mode packet.AEADMode , inputKey , salt , associatedData []byte ) (aead cipher.AEAD , nonce []byte , err error ) {
69+ hkdfReader := hkdf .New (sha256 .New , inputKey , salt , associatedData )
70+ encryptionKey := make ([]byte , c .KeySize ())
71+ _ , _ = io .ReadFull (hkdfReader , encryptionKey )
72+ nonce = make ([]byte , mode .IvLength ()- 8 )
73+ _ , _ = io .ReadFull (hkdfReader , nonce )
74+ blockCipher , err := blockCipher (c , encryptionKey )
75+ if err != nil {
76+ return
5777 }
58- packetParser := packet .NewReader (prefixReader )
59- _ , err = packetParser .Next ()
78+ aead , err = aeadMode (mode , blockCipher )
79+ return
80+ }
81+
82+ func checkSEIPDv1Decrypt (
83+ sessionKey * crypto.SessionKey ,
84+ prefixReader crypto.Reader ,
85+ ) (bool , error ) {
86+ cipher , err := sessionKey .GetCipherFunc ()
6087 if err != nil {
61- return false , errors .New ("gopenpgp: failed to parse packet prefix" )
88+ return false , errors .New ("gopenpgp: cipher algorithm not found" )
89+ }
90+ if ! supported (cipher ) {
91+ return false , errors .New ("gopenpgp: cipher not supported for quick check" )
6292 }
6393
64- blockSize := blockSize (algo )
94+ blockSize := blockSize (cipher )
6595 encryptedData := make ([]byte , blockSize + 2 )
66- _ , err = io .ReadFull (prefixReader , encryptedData )
67- if err != nil {
96+ if _ , err := io .ReadFull (prefixReader , encryptedData ); err != nil {
6897 return false , errors .New ("gopenpgp: prefix is too short to check" )
6998 }
7099
71- blockCipher , err := blockCipher (algo , sessionKey .Key )
100+ blockCipher , err := blockCipher (cipher , sessionKey .Key )
72101 if err != nil {
73102 return false , errors .New ("gopenpgp: failed to initialize the cipher" )
74103 }
75- _ = packet .NewOCFBDecrypter (blockCipher , encryptedData , packet .OCFBNoResync )
104+ packet .NewOCFBDecrypter (blockCipher , encryptedData , packet .OCFBNoResync )
76105 return encryptedData [blockSize - 2 ] == encryptedData [blockSize ] &&
77106 encryptedData [blockSize - 1 ] == encryptedData [blockSize + 1 ], nil
78107}
79108
109+ func checkSEIPDv2Decrypt (
110+ sessionKey * crypto.SessionKey ,
111+ symPacket * packet.SymmetricallyEncrypted ,
112+ ) (bool , error ) {
113+ if ! supported (symPacket .Cipher ) {
114+ return false , errors .New ("gopenpgp: cipher not supported for quick check" )
115+ }
116+ buffer := new (bytes.Buffer )
117+ aeadTagLength := symPacket .Mode .TagLength ()
118+ reader := symPacket .Contents
119+ var totalDataRead int64
120+ for {
121+ // Read up to copyChunkSize bytes into the buffer
122+ written , err := io .CopyN (buffer , reader , copyChunkSize - int64 (buffer .Len ()))
123+ totalDataRead += written
124+ // Discard all data from the buffer except last tag length bytes
125+ _ , _ = io .CopyN (ioutil .Discard , buffer , int64 (buffer .Len ())- int64 (aeadTagLength ))
126+ if errors .Is (err , io .EOF ) {
127+ break
128+ }
129+ if err != nil {
130+ return false , err
131+ }
132+ }
133+ totalDataRead -= int64 (aeadTagLength )
134+ aeadChunkSize := int64 (1 << (int64 (symPacket .ChunkSizeByte ) + 6 ))
135+ aeadChunkAndTagLength := aeadChunkSize + int64 (aeadTagLength )
136+ numberOfChunks := totalDataRead / aeadChunkAndTagLength
137+ if totalDataRead % aeadChunkAndTagLength != 0 {
138+ numberOfChunks += 1
139+ }
140+ plaintextLength := totalDataRead - numberOfChunks * int64 (aeadTagLength )
141+
142+ var amountBytes [8 ]byte
143+ var index [8 ]byte
144+ binary .BigEndian .PutUint64 (amountBytes [:], uint64 (plaintextLength ))
145+ binary .BigEndian .PutUint64 (index [:], uint64 (numberOfChunks ))
146+
147+ adata := []byte {
148+ 0xD2 ,
149+ byte (symPacket .Version ),
150+ byte (symPacket .Cipher ),
151+ byte (symPacket .Mode ),
152+ symPacket .ChunkSizeByte ,
153+ }
154+
155+ aead , nonce , err := getSymmetricallyEncryptedAeadInstance (symPacket .Cipher , symPacket .Mode , sessionKey .Key , symPacket .Salt [:], adata )
156+ if err != nil {
157+ return false , errors .New ("gopenpgp: failed to instantiate aead cipher" )
158+ }
159+ adata = append (adata , amountBytes [:]... )
160+ nonce = append (nonce , index [:]... )
161+ authenticationTag := buffer .Bytes ()
162+ _ , err = aead .Open (nil , nonce , authenticationTag , adata )
163+ return err == nil , nil
164+ }
165+
166+ // QuickCheckDecryptReader checks with high probability if the provided session key
167+ // can decrypt a data packet.
168+ // For SEIPDv1 it only uses a 24 byte long prefix of the data packet.
169+ // Thus, the function reads up to but not exactly 24 bytes from the prefixReader.
170+ // For SEIPDv2 the function reads the whole data packet.
171+ // NOTE: the function only works for data packets encrypted with AES.
172+ func QuickCheckDecryptReader (sessionKey * crypto.SessionKey , dataPacketReader crypto.Reader ) (bool , error ) {
173+ packetParser := packet .NewReader (dataPacketReader )
174+ p , err := packetParser .Next ()
175+ if err != nil {
176+ return false , errors .New ("gopenpgp: failed to parse packet prefix" )
177+ }
178+ if symPacket , ok := p .(* packet.SymmetricallyEncrypted ); ok {
179+ switch symPacket .Version {
180+ case 1 :
181+ return checkSEIPDv1Decrypt (sessionKey , dataPacketReader )
182+ case 2 :
183+ return checkSEIPDv2Decrypt (sessionKey , symPacket )
184+ }
185+ }
186+ return false , errors .New ("gopenpgp: no SEIPD packet found" )
187+ }
188+
80189// 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 ))
190+ // can decrypt the data packet.
191+ // For SEIPDv1 it only uses a 24 byte long prefix of the data packet (dataPacket[:24]).
192+ // For SEIPDv2 the function reads the whole data packet.
193+ // NOTE: the function only works for data packets encrypted with AES.
194+ func QuickCheckDecrypt (sessionKey * crypto.SessionKey , dataPacket []byte ) (bool , error ) {
195+ return QuickCheckDecryptReader (sessionKey , bytes .NewReader (dataPacket ))
86196}
0 commit comments