Skip to content

Commit 16334bf

Browse files
LinuxJedidanielinux
authored andcommitted
Add support for AES GCM streaming
1 parent cbbe08b commit 16334bf

7 files changed

Lines changed: 212 additions & 5 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ __pycache__/
66
# C extensions
77
*.so
88
*.a
9+
wolfcrypt/_ffi.*
910

1011
# Distribution / packaging
1112
.Python

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ Summary
1111
digest
1212
mac
1313
random
14+
streaming
1415

1516
.. include:: ../LICENSING.rst

docs/streaming.rst

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Streaming Encryption Algorithms
2+
===============================
3+
4+
.. module:: wolfcrypt.ciphers
5+
6+
Steaming Encryption Classes
7+
---------------------------
8+
9+
Interface
10+
~~~~~~~~~
11+
12+
AesGcmStreamEncrypt
13+
~~~~~~~~~~~~~~~~~~~
14+
15+
.. autoclass:: AesGcmStreamEncrypt
16+
:members:
17+
:inherited-members:
18+
19+
**Example:**
20+
21+
.. doctest::
22+
23+
>>> from wolfcrypt.ciphers import AesGcmStreamEncrypt
24+
>>> from binascii import hexlify as b2h
25+
>>> gcm = AesGcmStreamEncrypt(b'fedcba9876543210', b'0123456789abcdef')
26+
>>> buf = gcm.update("hello world")
27+
>>> authTag = gcm.final()
28+
>>> b2h(buf)
29+
b'5ba7d42e1bf01d7998e932'
30+
>>> b2h(authTag)
31+
b'cef91ba0c8c6431c7e19f64c9d9e371b'
32+
33+
AesGcmStreamDecrypt
34+
~~~~~~~~~~~~~~~~~~~
35+
36+
.. autoclass:: AesGcmStreamDecrypt
37+
:members:
38+
:inherited-members:
39+
40+
**Example:**
41+
42+
.. doctest::
43+
44+
>>> from wolfcrypt.ciphers import AesGcmStreamDecrypt, t2b
45+
>>> from binascii import unhexlify as h2b
46+
>>> gcm = AesGcmStreamDecrypt(b'fedcba9876543210', b'0123456789abcdef')
47+
>>> buf = gcm.update(h2b(b'5ba7d42e1bf01d7998e932'))
48+
>>> gcm.final(h2b(b'cef91ba0c8c6431c7e19f64c9d9e371b'))
49+
>>> t2b(buf)
50+
b'hello world'

tests/test_aesgcmstream.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# test_hashes.py
2+
#
3+
# Copyright (C) 2006-2022 wolfSSL Inc.
4+
#
5+
# This file is part of wolfSSL. (formerly known as CyaSSL)
6+
#
7+
# wolfSSL is free software; you can redistribute it and/or modify
8+
# it under the terms of the GNU General Public License as published by
9+
# the Free Software Foundation; either version 2 of the License, or
10+
# (at your option) any later version.
11+
#
12+
# wolfSSL is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU General Public License
18+
# along with this program; if not, write to the Free Software
19+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
20+
21+
# pylint: disable=redefined-outer-name
22+
23+
from collections import namedtuple
24+
import pytest
25+
from wolfcrypt._ffi import ffi as _ffi
26+
from wolfcrypt._ffi import lib as _lib
27+
from wolfcrypt.utils import t2b
28+
from binascii import hexlify as b2h, unhexlify as h2b
29+
30+
from wolfcrypt.ciphers import AesGcmStreamEncrypt, AesGcmStreamDecrypt
31+
32+
def test_encrypt():
33+
key = "fedcba9876543210"
34+
iv = "0123456789abcdef"
35+
gcm = AesGcmStreamEncrypt(key, iv)
36+
buf = gcm.update("hello world")
37+
authTag = gcm.final()
38+
assert b2h(authTag) == bytes('cef91ba0c8c6431c7e19f64c9d9e371b', 'utf-8')
39+
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
40+
gcmdec = AesGcmStreamDecrypt(key, iv)
41+
bufdec = gcmdec.update(buf)
42+
gcmdec.final(authTag)
43+
assert bufdec == t2b("hello world")
44+
45+
def test_multipart():
46+
key = "fedcba9876543210"
47+
iv = "0123456789abcdef"
48+
gcm = AesGcmStreamEncrypt(key, iv)
49+
buf = gcm.update("hello")
50+
buf += gcm.update(" world")
51+
authTag = gcm.final()
52+
assert b2h(authTag) == bytes('6862647a27c7b6aa0a6882b3e117e944', 'utf-8')
53+
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
54+
gcmdec = AesGcmStreamDecrypt(key, iv)
55+
bufdec = gcmdec.update(buf[:5])
56+
bufdec += gcmdec.update(buf[5:])
57+
gcmdec.final(authTag)
58+
assert bufdec == t2b("hello world")

wolfcrypt/_build_ffi.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def generate_libwolfssl():
102102
ERROR_STRINGS_ENABLED = 1
103103
ASN_ENABLED = 1
104104
WC_RNG_SEED_CB_ENABLED = 0
105+
AESGCM_STREAM = 1
105106

106107
# detect native features based on options.h defines
107108
if featureDetection:
@@ -126,6 +127,7 @@ def generate_libwolfssl():
126127
ERROR_STRINGS_ENABLED = 0 if '#define NO_ERROR_STRINGS' in optionsHeaderStr else 1
127128
ASN_ENABLED = 0 if '#define NO_ASN' in optionsHeaderStr else 1
128129
WC_RNG_SEED_CB_ENABLED = 1 if '#define WC_RNG_SEED_CB' in optionsHeaderStr else 0
130+
AESGCM_STREAM = 1 if '#define WOLFSSL_AESGCM_STREAM' in optionsHeaderStr else 0
129131

130132
if '#define HAVE_FIPS' in optionsHeaderStr:
131133
FIPS_ENABLED = 1
@@ -202,6 +204,7 @@ def generate_libwolfssl():
202204
int FIPS_VERSION = """ + str(FIPS_VERSION) + """;
203205
int ASN_ENABLED = """ + str(ASN_ENABLED) + """;
204206
int WC_RNG_SEED_CB_ENABLED = """ + str(WC_RNG_SEED_CB_ENABLED) + """;
207+
int AESGCM_STREAM = """ + str(AESGCM_STREAM) + """;
205208
""",
206209
include_dirs=[wolfssl_inc_path()],
207210
library_dirs=[wolfssl_lib_path()],
@@ -231,6 +234,7 @@ def generate_libwolfssl():
231234
extern int FIPS_VERSION;
232235
extern int ASN_ENABLED;
233236
extern int WC_RNG_SEED_CB_ENABLED;
237+
extern int AESGCM_STREAM;
234238
235239
typedef unsigned char byte;
236240
typedef unsigned int word32;
@@ -322,6 +326,27 @@ def generate_libwolfssl():
322326
int wc_AesCbcDecrypt(Aes*, byte*, const byte*, word32);
323327
"""
324328

329+
if AES_ENABLED and AESGCM_STREAM:
330+
_cdef += """
331+
int wc_AesInit(Aes* aes, void* heap, int devId);
332+
int wc_AesGcmInit(Aes* aes, const byte* key, word32 len,
333+
const byte* iv, word32 ivSz);
334+
int wc_AesGcmEncryptInit(Aes* aes, const byte* key, word32 len,
335+
const byte* iv, word32 ivSz);
336+
int wc_AesGcmEncryptInit_ex(Aes* aes, const byte* key, word32 len,
337+
byte* ivOut, word32 ivOutSz);
338+
int wc_AesGcmEncryptUpdate(Aes* aes, byte* out, const byte* in,
339+
word32 sz, const byte* authIn, word32 authInSz);
340+
int wc_AesGcmEncryptFinal(Aes* aes, byte* authTag,
341+
word32 authTagSz);
342+
int wc_AesGcmDecryptInit(Aes* aes, const byte* key, word32 len,
343+
const byte* iv, word32 ivSz);
344+
int wc_AesGcmDecryptUpdate(Aes* aes, byte* out, const byte* in,
345+
word32 sz, const byte* authIn, word32 authInSz);
346+
int wc_AesGcmDecryptFinal(Aes* aes, const byte* authTag,
347+
word32 authTagSz);
348+
"""
349+
325350
if CHACHA_ENABLED:
326351
_cdef += """
327352
typedef struct { ...; } ChaCha;

wolfcrypt/_build_wolfssl.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def make_flags(prefix):
143143
flags.append("-DWOLFSSL_AES=yes")
144144
flags.append("-DWOLFSSL_DES3=yes")
145145
flags.append("-DWOLFSSL_CHACHA=yes")
146-
flags.append("-DWOLFSSL_AESGCM=no")
146+
flags.append("-DWOLFSSL_AESGCM=yes")
147147
flags.append("-DWOLFSSL_SHA=yes")
148148
flags.append("-DWOLFSSL_SHA384=yes")
149149
flags.append("-DWOLFSSL_SHA512=yes")
@@ -165,7 +165,7 @@ def make_flags(prefix):
165165
flags.append("-DWOLFSSL_EXTENDED_MASTER=no")
166166
flags.append("-DWOLFSSL_ERROR_STRINGS=no")
167167
# Part of hack for missing CMake option
168-
flags.append("-DCMAKE_C_FLAGS=\"/DWOLFSSL_KEY_GEN=1 /DWOLFCRYPT_ONLY=1\"")
168+
flags.append("-DCMAKE_C_FLAGS=\"/DWOLFSSL_KEY_GEN=1 /DWOLFCRYPT_ONLY=1 /DWOLFSSL_AESGCM_STREAM=1\"")
169169

170170
return " ".join(flags)
171171
else:
@@ -187,7 +187,9 @@ def make_flags(prefix):
187187
flags.append("--enable-des3")
188188
flags.append("--enable-chacha")
189189

190-
flags.append("--disable-aesgcm")
190+
flags.append("--enable-aesgcm-stream")
191+
192+
flags.append("--enable-aesgcm")
191193

192194
# hashes and MACs
193195
flags.append("--enable-sha")
@@ -233,6 +235,8 @@ def cmake_hack():
233235
contents.insert(27, "#define WOLFSSL_KEY_GEN\n")
234236
contents.insert(28, "#undef WOLFCRYPT_ONLY\n")
235237
contents.insert(29, "#define WOLFCRYPT_ONLY\n")
238+
contents.insert(30, "#undef WOLFSSL_AESGCM_STREAM\n")
239+
contents.insert(31, "#define WOLFSSL_AESGCM_STREAM\n")
236240

237241
with open(options_file, "w") as f:
238242
contents = "".join(contents)
@@ -273,9 +277,9 @@ def build_wolfssl(version="master"):
273277
else:
274278
libfile = os.path.join(prefix, 'lib/libwolfssl.la')
275279

276-
rebuild = ensure_wolfssl_src(version)
280+
ensure_wolfssl_src(version)
277281

278-
if rebuild or not os.path.isfile(libfile):
282+
if not os.path.isfile(libfile):
279283
make(make_flags(prefix))
280284

281285
if __name__ == "__main__":

wolfcrypt/ciphers.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,74 @@ def _decrypt(self, destination, source):
230230
return _lib.wc_AesCbcDecrypt(self._dec, destination,
231231
source, len(source))
232232

233+
if _lib.AESGCM_STREAM:
234+
class _AesGcmStream(object):
235+
"""
236+
AES GCM Stream
237+
"""
238+
block_size = 16
239+
_key_sizes = [16, 24, 32]
240+
_native_type = "Aes *"
241+
242+
def __init__(self, key, IV):
243+
key = t2b(key)
244+
IV = t2b(IV)
245+
if len(key) not in self._key_sizes:
246+
raise ValueError("key must be %s in length, not %d" %
247+
(self._key_sizes, len(key)))
248+
self._native_object = _ffi.new(self._native_type)
249+
_lib.wc_AesInit(self._native_object, _ffi.NULL, -2)
250+
self._authIn = _ffi.new("byte[%d]" % self.block_size)
251+
ret = _lib.wc_AesGcmInit(self._native_object, key, len(key), IV, len(IV))
252+
if ret < 0:
253+
raise WolfCryptError("Init error (%d)" % ret)
254+
255+
def update(self, data):
256+
"""
257+
Updates the stream with another segment of data.
258+
"""
259+
ret = 0
260+
data = t2b(data)
261+
self._buf = _ffi.new("byte[%d]" % (len(data)))
262+
ret = self._update(data)
263+
if ret < 0:
264+
raise WolfCryptError("Decryption error (%d)" % ret)
265+
return bytes(self._buf)
266+
267+
class AesGcmStreamEncrypt(_AesGcmStream):
268+
"""
269+
AES GCM Streaming Encryption
270+
"""
271+
def _update(self, data):
272+
return _lib.wc_AesGcmEncryptUpdate(self._native_object, self._buf, data, len(data), self._authIn, self.block_size)
273+
274+
def final(self):
275+
"""
276+
Finalize the stream and return an authentication tag for the stream.
277+
"""
278+
authTag = _ffi.new("byte[%d]" % self.block_size)
279+
ret = _lib.wc_AesGcmEncryptFinal(self._native_object, authTag, self.block_size)
280+
if ret < 0:
281+
raise WolfCryptError("Encryption error (%d)" % ret)
282+
return _ffi.buffer(authTag)[:]
283+
284+
285+
class AesGcmStreamDecrypt(_AesGcmStream):
286+
"""
287+
AES GCM Streaming Decryption
288+
"""
289+
def _update(self, data):
290+
return _lib.wc_AesGcmDecryptUpdate(self._native_object, self._buf, data, len(data), self._authIn, self.block_size)
291+
292+
def final(self, authTag):
293+
"""
294+
Finalize the stream and verify using the provided authentication tag.
295+
"""
296+
authTag = t2b(authTag)
297+
ret = _lib.wc_AesGcmDecryptFinal(self._native_object, authTag, self.block_size)
298+
if ret < 0:
299+
raise WolfCryptError("Decryption error (%d)" % ret)
300+
233301
if _lib.CHACHA_ENABLED:
234302
class ChaCha(_Cipher):
235303
"""

0 commit comments

Comments
 (0)