oss-sec mailing list archives

Re: Default IV & other issues in aes-js & pyaes modules, & strongMan VPN manager


From: Soatok Dreamseeker <soatok.dhole () gmail com>
Date: Thu, 19 Feb 2026 13:35:10 -0500

Hi Alan,

On Thu, Feb 19, 2026 at 1:27 PM Alan Coopersmith <alan.coopersmith () oracle com> wrote:
https://blog.trailofbits.com/2026/02/18/carelessness-versus-craftsmanship-in-cryptography/
reports:
Two popular AES libraries, aes-js and pyaes, “helpfully” provide a default IV
in their AES-CTR API, leading to a large number of key/IV reuse bugs. These
bugs potentially affect thousands of downstream projects.

strongMan is a web-based management tool for folks using the strongSwan VPN
suite. It allows for credential and user management, initiation of VPN
connections, and more. It’s a pretty slick piece of software; if you’re into
IPsec VPNs, you should definitely give it a look.

There will be a security advisory for strongMan issued in conjunction with this
fix, outlining the nature of the problem, its severity, and the measures taken
to address it. Everything will be out in the open, with full transparency for
all strongMan users.

That strongMan patch is refreshing to see:

The latest version fixes the issue by switching to AES-GCM-SIV encryption
with a random nonce and an individually derived encryption key, using HKDF,
for each encrypted value. Database migrations are provided to automatically
re-encrypt all credentials.


The patch is also pretty small:

diff --git a/requirements.txt b/requirements.txt
index 6cf1caa..111fa76 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,7 @@
 Django==4.2.28
 git+https://github.com/wbond/oscrypto.git@1547f535001ba568b239b8797465536759c742a3
 asn1crypto==1.5.1
+cryptography==46.0.3
 pyaes==1.6.1
 django-tables2==2.3.4
 vici==5.8.4
diff --git a/strongMan/helper_apps/encryption/fields.py
b/strongMan/helper_apps/encryption/fields.py
index f574782..3af11eb 100644
--- a/strongMan/helper_apps/encryption/fields.py
+++ b/strongMan/helper_apps/encryption/fields.py
@@ -1,11 +1,16 @@
 '''
 https://github.com/orcasgit/django-fernet-fields
 '''
+import os
+
 from django.conf import settings
 from django.core.exceptions import FieldError, ImproperlyConfigured
 from django.db import models
 from django.utils.encoding import force_bytes, force_str
 from django.utils.functional import cached_property
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.ciphers.aead import AESGCMSIV
+from cryptography.hazmat.primitives.kdf.hkdf import HKDF
 from pyaes import aes as aeslib

 __all__ = [
@@ -20,7 +25,7 @@


 class EncryptedField(models.Field):
-    """A field that encrypts values using Fernet symmetric encryption."""
+    """A field that encrypts values using AES-GCM-SIV symmetric encryption."""
     _internal_type = 'BinaryField'

     def __init__(self, *args, **kwargs):
@@ -42,12 +47,24 @@ def __init__(self, *args, **kwargs):
         super(EncryptedField, self).__init__(*args, **kwargs)

     def encrypt(self, value):
-        aes = aeslib.AESModeOfOperationCTR(self.key)
-        return aes.encrypt(value)
+        # we use a random nonce for the encryption and to generate an
individual encryption key, which is
+        # then concatenated and separated by a : from the ciphertext
+        nonce = os.urandom(12)
+        # leave the salt intentionally blank
+        hkdf = HKDF(algorithm=hashes.SHA256(), length=32, salt=b'',
info=nonce + b'EncryptedField')
+        aesgcmsiv = AESGCMSIV(hkdf.derive(self.key))
+        return nonce + b':' + aesgcmsiv.encrypt(nonce, value, None)

     def decrypt(self, value):
-        aes = aeslib.AESModeOfOperationCTR(self.key)
-        return aes.decrypt(value)
+        # decrypt unsafe legacy values if we don't find a nonce
+        if len(value) < 13 or value[12] != b':'[0]:
+            aes = aeslib.AESModeOfOperationCTR(self.key)
+            return aes.decrypt(value)
+        # <12-byte nonce>:<ciphertext>
+        nonce = value[:12]
+        hkdf = HKDF(algorithm=hashes.SHA256(), length=32, salt=b'',
info=nonce + b'EncryptedField')
+        aesgcmsiv = AESGCMSIV(hkdf.derive(self.key))
+        return aesgcmsiv.decrypt(nonce, value[13:], None)

     @cached_property

     def key(self):


Hell, it even uses HKDF correctly
<https://soatok.blog/2021/11/17/understanding-hkdf/>. Most people screw
HKDF up in a way that only security proof authors really care about, and
they didn't.

Just wanted to say: As awful as pyaes and aes-js are, seeing this high
quality work in response to the disclosure is a little heartwarming.

Current thread: