From 4c51465a0efb3c00636a8fd4f550ae2b0878e5c8 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Thu, 27 Aug 2026 11:00:27 +0200 Subject: [PATCH] feat: add Crypt::Rijndael Java crypto backend Implement the Crypt::Rijndael 1.16 XS API through Java's AES provider, including ECB, CBC, CFB128, OFB, and CTR modes. Bundle the original upstream tests and focused NIST vector regression coverage. Generated with [Codex](https://openai.com/codex/) Co-Authored-By: Codex --- docs/about/changelog.md | 2 + docs/reference/bundled-modules.md | 2 + docs/reference/feature-matrix.md | 2 + .../runtime/perlmodule/CryptRijndael.java | 161 +++++++++++++ src/main/perl/lib/Crypt/Rijndael.pm | 37 +++ .../module/Crypt-Rijndael/t/00_load.t | 211 ++++++++++++++++++ .../resources/module/Crypt-Rijndael/t/modes.t | 90 ++++++++ .../module/Crypt-Rijndael/t/rijndael.t | 35 +++ .../module/Crypt-Rijndael/t/rt/27632.t | 43 ++++ 9 files changed, 583 insertions(+) create mode 100644 src/main/java/org/perlonjava/runtime/perlmodule/CryptRijndael.java create mode 100644 src/main/perl/lib/Crypt/Rijndael.pm create mode 100644 src/test/resources/module/Crypt-Rijndael/t/00_load.t create mode 100644 src/test/resources/module/Crypt-Rijndael/t/modes.t create mode 100644 src/test/resources/module/Crypt-Rijndael/t/rijndael.t create mode 100644 src/test/resources/module/Crypt-Rijndael/t/rt/27632.t diff --git a/docs/about/changelog.md b/docs/about/changelog.md index 44e7374fa2..7ff0db5861 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -15,6 +15,8 @@ Release history of PerlOnJava. See [Roadmap](roadmap.md) for future plans. `rmtree`/`remove_tree` options such as `keep_root`, `error`, `result`, `safe`, and `verbose`. - Add `Time::Moment` 0.46 as a Java-backed bundled provider using `java.time`. +- Add `Crypt::Rijndael` 1.16 as a Java-backed AES provider with ECB, CBC, + CFB128, OFB, and CTR compatibility. ## v5.44.1: Regex, Threads, Async/Await, and CPAN Compatibility diff --git a/docs/reference/bundled-modules.md b/docs/reference/bundled-modules.md index 7e8f1fb10d..51010ec813 100644 --- a/docs/reference/bundled-modules.md +++ b/docs/reference/bundled-modules.md @@ -25,6 +25,7 @@ Recent CPAN compatibility additions include: | `Encode::Locale` | Pure Perl facade over bundled locale support | Provides LWP and XML::Parser with locale encoding aliases; includes `Encode::Alias` | | `Crypt::Twofish2` | Java XS bridge | BouncyCastle Twofish engine with ECB, CBC, and CFB1 compatibility | | `Crypt::Blowfish` | Java XS bridge | BouncyCastle Blowfish engine used by `Crypt::CBC` and `Git::Crypt` | +| `Crypt::Rijndael` | Java XS bridge | JCA AES provider with ECB, CBC, CFB128, OFB, and CTR compatibility | | `Proc::ProcessTable` | Perl + Java XS bridge | Portable process enumeration and common fields via Java `ProcessHandle` | | `Tie::Array::Packed` | Perl + Java XS bridge | Packed scalar storage for the upstream tied-array API and pack formats | | `Digest::JHash` | Java XS bridge | Jenkins 32-bit hash used by CHI and TimeZone::TimeZoneDB | @@ -304,6 +305,7 @@ These are loaded automatically or via `use`: | `Digest::SHA` | Java | `java.security.MessageDigest` | | `Digest` | Perl | | | `Crypt::Twofish2` | Java + Perl | Uses the bundled BouncyCastle provider; passes all 83 upstream vectors | +| `Crypt::Rijndael` | Java + Perl | Uses the JCA AES provider; supports the upstream ECB, CBC, CFB128, OFB, and CTR API | | `MIME::Base64` | Java | | | `MIME::QuotedPrint` | Java | | | `Encode` | Java + Perl | | diff --git a/docs/reference/feature-matrix.md b/docs/reference/feature-matrix.md index 41f3820198..4553e01f3d 100644 --- a/docs/reference/feature-matrix.md +++ b/docs/reference/feature-matrix.md @@ -812,6 +812,8 @@ The `:encoding()` layer supports all encodings provided by Java's `Charset.forNa enumeration and common process fields through `ProcessHandle`. - ✅ **Crypt::Twofish2**: Java XS replacement backed by BouncyCastle, with upstream-compatible ECB, CBC, and CFB1 modes. +- ✅ **Crypt::Rijndael**: Java XS replacement backed by the JCA AES provider, + with upstream-compatible ECB, CBC, CFB128, OFB, and CTR modes. - ✅ **Tie::Array::Packed**: Java XS replacement for packed tied-array storage, mutation, splicing, rotation, and binary search. - 🟡 **B::Flags**: portable OP/SV flag names over the bundled partial `B` diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/CryptRijndael.java b/src/main/java/org/perlonjava/runtime/perlmodule/CryptRijndael.java new file mode 100644 index 0000000000..82b5105c70 --- /dev/null +++ b/src/main/java/org/perlonjava/runtime/perlmodule/CryptRijndael.java @@ -0,0 +1,161 @@ +package org.perlonjava.runtime.perlmodule; + +import org.perlonjava.frontend.parser.StringParser; +import org.perlonjava.runtime.operators.ReferenceOperators; +import org.perlonjava.runtime.runtimetypes.PerlCompilerException; +import org.perlonjava.runtime.runtimetypes.RuntimeArray; +import org.perlonjava.runtime.runtimetypes.RuntimeHash; +import org.perlonjava.runtime.runtimetypes.RuntimeList; +import org.perlonjava.runtime.runtimetypes.RuntimeScalar; +import org.perlonjava.runtime.runtimetypes.RuntimeScalarType; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.util.Arrays; + +/** Java XS replacement for Crypt::Rijndael 1.16, using the JCA AES provider. */ +public final class CryptRijndael extends PerlModuleBase { + private static final String CLASS_NAME = "Crypt::Rijndael"; + private static final String STATE_KEY = "_rijndael_state"; + private static final int BLOCK_SIZE = 16; + private static final int MODE_ECB = 1; + private static final int MODE_CBC = 2; + private static final int MODE_CFB = 3; + private static final int MODE_PCBC = 4; + private static final int MODE_OFB = 5; + private static final int MODE_CTR = 6; + + public CryptRijndael() { + super(CLASS_NAME, false); + } + + public static void initialize() { + CryptRijndael module = new CryptRijndael(); + try { + module.registerMethod("new", "new_", null); + module.registerMethod("keysize", null); + module.registerMethod("blocksize", null); + module.registerMethod("set_iv", null); + module.registerMethod("encrypt", null); + module.registerMethod("decrypt", null); + module.registerMethod("DESTROY", null); + module.registerMethod("MODE_ECB", null); + module.registerMethod("MODE_CBC", null); + module.registerMethod("MODE_CFB", null); + module.registerMethod("MODE_PCBC", null); + module.registerMethod("MODE_OFB", null); + module.registerMethod("MODE_CTR", null); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Unable to initialize " + CLASS_NAME, e); + } + } + + public static RuntimeList keysize(RuntimeArray args, int ctx) { return scalar(32); } + public static RuntimeList blocksize(RuntimeArray args, int ctx) { return scalar(BLOCK_SIZE); } + public static RuntimeList MODE_ECB(RuntimeArray args, int ctx) { return scalar(MODE_ECB); } + public static RuntimeList MODE_CBC(RuntimeArray args, int ctx) { return scalar(MODE_CBC); } + public static RuntimeList MODE_CFB(RuntimeArray args, int ctx) { return scalar(MODE_CFB); } + public static RuntimeList MODE_PCBC(RuntimeArray args, int ctx) { return scalar(MODE_PCBC); } + public static RuntimeList MODE_OFB(RuntimeArray args, int ctx) { return scalar(MODE_OFB); } + public static RuntimeList MODE_CTR(RuntimeArray args, int ctx) { return scalar(MODE_CTR); } + + public static RuntimeList new_(RuntimeArray args, int ctx) { + if (args.size() < 2) { + throw new PerlCompilerException("Usage: Crypt::Rijndael::new(class, key, mode=MODE_ECB)"); + } + RuntimeScalar keyArg = args.get(1); + if (keyArg.type != RuntimeScalarType.STRING && keyArg.type != RuntimeScalarType.BYTE_STRING) { + throw new PerlCompilerException("Key must be an string scalar"); + } + byte[] key = bytes(keyArg, "new"); + if (key.length != 16 && key.length != 24 && key.length != 32) { + throw new PerlCompilerException("Wrong key length: key must be 128, 192 or 256 bits long"); + } + int mode = args.size() > 2 ? args.get(2).getInt() : MODE_ECB; + if (mode != MODE_ECB && mode != MODE_CBC && mode != MODE_CFB && mode != MODE_OFB && mode != MODE_CTR) { + throw new PerlCompilerException("Illegal mode, see documentation for valid modes"); + } + RuntimeHash self = new RuntimeHash(); + self.put(STATE_KEY, new RuntimeScalar(new State(key, mode))); + RuntimeScalar ref = self.createReference(); + String className = args.get(0).toString(); + ReferenceOperators.bless(ref, new RuntimeScalar(className.isEmpty() ? CLASS_NAME : className)); + return ref.getList(); + } + + public static RuntimeList set_iv(RuntimeArray args, int ctx) { + if (args.size() < 2) throw new PerlCompilerException("Usage: Crypt::Rijndael::set_iv(self, data)"); + byte[] iv = bytes(args.get(1), "set_iv"); + if (iv.length != BLOCK_SIZE) throw new PerlCompilerException("set_iv: IV must be 16 bytes long"); + State state = state(args.get(0)); + System.arraycopy(iv, 0, state.iv, 0, BLOCK_SIZE); + return args.get(0).getList(); + } + + public static RuntimeList encrypt(RuntimeArray args, int ctx) { return crypt(args, true); } + public static RuntimeList decrypt(RuntimeArray args, int ctx) { return crypt(args, false); } + public static RuntimeList DESTROY(RuntimeArray args, int ctx) { return new RuntimeList(); } + + private static RuntimeList crypt(RuntimeArray args, boolean encrypt) { + if (args.size() < 2) { + throw new PerlCompilerException("Usage: Crypt::Rijndael::" + (encrypt ? "encrypt" : "decrypt") + "(self, data, iv=self->iv)"); + } + State state = state(args.get(0)); + byte[] input = bytes(args.get(1), encrypt ? "encrypt" : "decrypt"); + if ((state.mode == MODE_ECB || state.mode == MODE_CBC) && input.length % BLOCK_SIZE != 0) { + throw new PerlCompilerException("encrypt: datasize not multiple of blocksize (16 bytes)"); + } + byte[] iv = args.size() > 2 ? bytes(args.get(2), encrypt ? "encrypt" : "decrypt") : state.iv; + if (state.mode != MODE_ECB && iv.length != BLOCK_SIZE) { + throw new PerlCompilerException("encrypt: IV must be 16 bytes long"); + } + try { + Cipher cipher = Cipher.getInstance(transformation(state.mode)); + SecretKeySpec key = new SecretKeySpec(state.key, "AES"); + if (state.mode == MODE_ECB) cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key); + else cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); + return new RuntimeScalar(cipher.doFinal(input)).getList(); + } catch (GeneralSecurityException e) { + throw new PerlCompilerException("Crypt::Rijndael " + (encrypt ? "encrypt" : "decrypt") + " failed: " + e.getMessage()); + } + } + + private static String transformation(int mode) { + return switch (mode) { + case MODE_ECB -> "AES/ECB/NoPadding"; + case MODE_CBC -> "AES/CBC/NoPadding"; + case MODE_CFB -> "AES/CFB/NoPadding"; + case MODE_OFB -> "AES/OFB/NoPadding"; + case MODE_CTR -> "AES/CTR/NoPadding"; + default -> throw new PerlCompilerException("Illegal mode, see documentation for valid modes"); + }; + } + + private static byte[] bytes(RuntimeScalar value, String operation) { + String string = value.toString(); + StringParser.assertNoWideCharacters(string, operation); + return string.getBytes(StandardCharsets.ISO_8859_1); + } + + private static State state(RuntimeScalar selfRef) { + RuntimeScalar stored = selfRef.hashDeref().get(STATE_KEY); + if (stored != null && stored.type == RuntimeScalarType.JAVAOBJECT && stored.value instanceof State state) return state; + throw new PerlCompilerException("Crypt::Rijndael object has invalid state"); + } + + private static RuntimeList scalar(int value) { return new RuntimeScalar(value).getList(); } + + private static final class State { + private final byte[] key; + private final int mode; + private final byte[] iv = new byte[BLOCK_SIZE]; + + private State(byte[] key, int mode) { + this.key = Arrays.copyOf(key, key.length); + this.mode = mode; + } + } +} diff --git a/src/main/perl/lib/Crypt/Rijndael.pm b/src/main/perl/lib/Crypt/Rijndael.pm new file mode 100644 index 0000000000..e0c12559a8 --- /dev/null +++ b/src/main/perl/lib/Crypt/Rijndael.pm @@ -0,0 +1,37 @@ +package Crypt::Rijndael; + +use strict; +use warnings; + +our $VERSION = '1.16'; + +use XSLoader; +XSLoader::load('Crypt::Rijndael', $VERSION); + +1; + +__END__ + +=head1 NAME + +Crypt::Rijndael - Crypt::CBC compliant Rijndael encryption module + +=head1 DESCRIPTION + +This PerlOnJava port preserves the Crypt::Rijndael 1.16 XS API and uses the +Java Cryptography Architecture AES provider. It supports AES's 128-bit block +size and 128-, 192-, and 256-bit keys in ECB, CBC, CFB128, OFB, and CTR modes. +PCBC remains unsupported, matching the upstream distribution. + +=head1 AUTHOR + +Currently maintained by Leon Timmermans Eleont@cpan.orgE. + +Previously maintained by brian d foy. Original code by Rafael R. Sevilla. + +=head1 LICENSE + +This wrapper preserves the interface and attribution of Crypt::Rijndael 1.16, +which is licensed under the Lesser GNU Public License v3 or later. + +=cut diff --git a/src/test/resources/module/Crypt-Rijndael/t/00_load.t b/src/test/resources/module/Crypt-Rijndael/t/00_load.t new file mode 100644 index 0000000000..80d72b6e58 --- /dev/null +++ b/src/test/resources/module/Crypt-Rijndael/t/00_load.t @@ -0,0 +1,211 @@ +#! perl +use strict; +use warnings; + +use Test::More tests => 40; +use Crypt::Rijndael; + +my %flag_for = ( + ecb => Crypt::Rijndael::MODE_ECB, + cbc => Crypt::Rijndael::MODE_CBC, + cfb => Crypt::Rijndael::MODE_CFB, + ofb => Crypt::Rijndael::MODE_OFB, + ctr => Crypt::Rijndael::MODE_CTR, +); + +sub is_crypted { + my %args = @_; + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my $cipher = Crypt::Rijndael->new(pack('H*', $args{key}), $flag_for{ $args{mode} }); + $cipher->set_iv(pack 'H*', $args{iv}) if defined $args{iv}; + + my $plaintext = pack 'H*', $args{plaintext}; + my $crypted = $cipher->encrypt($plaintext); + + is(unpack("H*", $crypted), $args{ciphertext}); + is($cipher->decrypt($crypted), $plaintext); +} + +my $plaintext = unpack "H*", pack "C32", 0 .. 31; +my $key = unpack "H*", pack 'Cx31', 1; + +is_crypted( + name => 'ECB-AES-256-', + key => $key, + mode => 'ecb', + plaintext => $plaintext, + ciphertext => "f2258e225d794572393a6484cfced7cf925d1aa18366bcd93c33d104294c8a6f", +); + +is_crypted( + name => '', + key => $key, + mode => 'cbc', + plaintext => $plaintext, + ciphertext => "f2258e225d794572393a6484cfced7cfb487a41f6b6286c00c9c8d80cb3ee9f8", +); + +$plaintext = unpack "H*", pack 'C*', map { $_ + ($_ << 4) } 0 .. 0xF; + +is_crypted( + name => 'AES-256', + key => unpack('H*', pack('C*', 0 .. 31)), + mode => 'ecb', + plaintext => $plaintext, + ciphertext => "8ea2b7ca516745bfeafc49904b496089", +); + +is_crypted( + name => 'AES-192', + key => unpack( 'H*', pack('C*', 0 .. 23)), + mode => 'ecb', + plaintext => $plaintext, + ciphertext => "dda97ca4864cdfe06eaf70a0ec0d7191", +); + +is_crypted( + name => 'AES-128', + key => unpack('H*', pack('C*', 0 .. 15)), + mode => 'ecb', + plaintext => $plaintext, + ciphertext => "69c4e0d86a7b0430d8cdb78070b4c55a", +); + +# Modes of operation -- NIST paper tests + + +is_crypted( + name => 'ECB-AES-128', + key => "2b7e151628aed2a6abf7158809cf4f3c", + mode => 'ecb', + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "3ad77bb40d7a3660a89ecaf32466ef97f5d3d58503b9699de785895a96fdbaaf43b1cd7f598ece23881b00e3ed0306887b0c785e27e8ad3f8223207104725dd4", +); + +is_crypted( + name => 'ECB-AES-192', + key => "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + mode => 'ecb', + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "bd334f1d6e45f25ff712a214571fa5cc974104846d0ad3ad7734ecb3ecee4eefef7afd2270e2e60adce0ba2face6444e9a4b41ba738d6c72fb16691603c18e0e", +); + +is_crypted( + name => 'ECB-AES-256', + key => "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + mode => 'ecb', + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "f3eed1bdb5d2a03c064b5a7e3db181f8591ccb10d410ed26dc5ba74a31362870b6ed21b99ca6f4f9f153e7b1beafed1d23304b7a39f9f3ff067d8d8f9e24ecc7", +); + +is_crypted( + name => 'CBC-AES-128', + key => "2b7e151628aed2a6abf7158809cf4f3c", + mode => 'cbc', + iv => "000102030405060708090a0b0c0d0e0f", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7", +); + +is_crypted( + name => 'CBC-AES-192', + key => "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + mode => 'cbc', + iv => "000102030405060708090a0b0c0d0e0f", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "4f021db243bc633d7178183a9fa071e8b4d9ada9ad7dedf4e5e738763f69145a571b242012fb7ae07fa9baac3df102e008b0e27988598881d920a9e64f5615cd", +); + +is_crypted( + name => 'CBC-AES-256', + key => "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + mode => 'cbc', + iv => "000102030405060708090a0b0c0d0e0f", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b", +); + +is_crypted( + name => 'CFB-AES-128', + key => "2b7e151628aed2a6abf7158809cf4f3c", + mode => 'cfb', + iv => "000102030405060708090a0b0c0d0e0f", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "3b3fd92eb72dad20333449f8e83cfb4ac8a64537a0b3a93fcde3cdad9f1ce58b26751f67a3cbb140b1808cf187a4f4dfc04b05357c5d1c0eeac4c66f9ff7f2e6", +); + +is_crypted( + name => 'CFB-AES-192', + key => "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + mode => 'cfb', + iv => "000102030405060708090a0b0c0d0e0f", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "cdc80d6fddf18cab34c25909c99a417467ce7f7f81173621961a2b70171d3d7a2e1e8a1dd59b88b1c8e60fed1efac4c9c05f9f9ca9834fa042ae8fba584b09ff", +); + +is_crypted( + name => 'CFB-AES-256', + key => "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + mode => 'cfb', + iv => "000102030405060708090a0b0c0d0e0f", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "dc7e84bfda79164b7ecd8486985d386039ffed143b28b1c832113c6331e5407bdf10132415e54b92a13ed0a8267ae2f975a385741ab9cef82031623d55b1e471", +); + +is_crypted( + name => 'OFB-AES-128', + key => "2b7e151628aed2a6abf7158809cf4f3c", + mode => 'ofb', + iv => "000102030405060708090a0b0c0d0e0f", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "3b3fd92eb72dad20333449f8e83cfb4a7789508d16918f03f53c52dac54ed8259740051e9c5fecf64344f7a82260edcc304c6528f659c77866a510d9c1d6ae5e", +); + +# OFB-AES-192 +is_crypted( + name => 'OFB-AES-192', + key => "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + mode => 'ofb', + iv => "000102030405060708090a0b0c0d0e0f", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "cdc80d6fddf18cab34c25909c99a4174fcc28b8d4c63837c09e81700c11004018d9a9aeac0f6596f559c6d4daf59a5f26d9f200857ca6c3e9cac524bd9acc92a", +); + +# OFB-AES-256 +is_crypted( + name => 'OFB-AES-256', + key => "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + mode => 'ofb', + iv => "000102030405060708090a0b0c0d0e0f", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "dc7e84bfda79164b7ecd8486985d38604febdc6740d20b3ac88f6ad82a4fb08d71ab47a086e86eedf39d1c5bba97c4080126141d67f37be8538f5a8be740e484", +); + +is_crypted( + name => 'CTR-AES-128', + key => "2b7e151628aed2a6abf7158809cf4f3c", + mode => 'ctr', + iv => "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee", +); + +is_crypted( + name => 'CTR-AES-192', + key => "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + mode => 'ctr', + iv => "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "1abc932417521ca24f2b0459fe7e6e0b090339ec0aa6faefd5ccc2c6f4ce8e941e36b26bd1ebc670d1bd1d665620abf74f78a7f6d29809585a97daec58c6b050", +); + +is_crypted( + name => 'CTR-AES-256', + key => "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + mode => 'ctr', + iv => "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + plaintext => "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + ciphertext => "601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6", +); + diff --git a/src/test/resources/module/Crypt-Rijndael/t/modes.t b/src/test/resources/module/Crypt-Rijndael/t/modes.t new file mode 100644 index 0000000000..55af353c9e --- /dev/null +++ b/src/test/resources/module/Crypt-Rijndael/t/modes.t @@ -0,0 +1,90 @@ +#!/usr/bin/perl + +use Test::More tests => 90; + +use Crypt::Rijndael; + +ok(defined &Crypt::Rijndael::blocksize); +is(Crypt::Rijndael->blocksize, 16); + +foreach my $a ( 0 .. 10 ) { + my $hash = crypt_decrypt(Crypt::Rijndael::MODE_CBC); + is($hash->{plain}, $hash->{data}, "Decrypted text matches plain text for cbc-$a"); +} + +foreach my $a ( 0 .. 10 ) { + my $hash = crypt_decrypt(Crypt::Rijndael::MODE_CFB); + is($hash->{plain}, $hash->{data}, "Decrypted text matches plain text for cfb-$a"); +} + +foreach my $a ( 0 .. 10 ) { + my ($plain, $data) = crypt_decrypt_partial(Crypt::Rijndael::MODE_CFB); + is($plain, $data, "Decrypted text matches plain text for cfb-$a-partial"); +} + +foreach my $a ( 0 .. 10 ) { + my $hash = crypt_decrypt(Crypt::Rijndael::MODE_CTR); + is($hash->{plain}, $hash->{data}, "Decrypted text matches plain text for ctr-$a"); +} + +foreach my $a ( 0 .. 10 ) { + my $hash = crypt_decrypt(Crypt::Rijndael::MODE_ECB); + is($hash->{plain}, $hash->{data}, "Decrypted text matches plain text for ecb-$a"); +} + +foreach my $a ( 0 .. 10 ) { + my $hash = crypt_decrypt(Crypt::Rijndael::MODE_OFB ); + is($hash->{plain}, $hash->{data}, "Decrypted text matches plain text for ofb-$a"); +} + +foreach my $a ( 0 .. 10 ) { + my ($plain, $data) = crypt_decrypt_partial(Crypt::Rijndael::MODE_OFB); + is($plain, $data, "Decrypted text matches plain text for ofb-$a-partial"); +} + +TODO: { + todo_skip "PCBC is not a legal mode (yet)", 11; + + foreach my $a ( 0 .. 10 ) { + my $hash = crypt_decrypt(Crypt::Rijndael::MODE_PCBC); + is($hash->{plain}, $hash->{data}, "Decrypted text matches plain text"); + } + +}; + +sub crypt_decrypt { + my $mode = shift; + + my $key = make_string(32); + my $c = Crypt::Rijndael->new($key, $mode); + my $data = make_string(32 * int rand(16) + 1); + my $iv = make_string(16); + + my $cipher = $c->encrypt($data, $iv); + my $plain = $c->decrypt($cipher, $iv); + + return { + data => $data, + cipher => $cipher, + plain => $plain, + }; +} + +sub crypt_decrypt_partial { + my $mode = shift; + + my $key = make_string(16); + my $c = Crypt::Rijndael->new($key, $mode); + my $data = make_string(32 * int(2 + 1) + 8); + my $iv = make_string(16); + + my $cipher = $c->encrypt($data, $iv); + my $plain = $c->decrypt($cipher, $iv); + + return ($plain, $data); +} + +sub make_string { + my $size = shift; + return pack 'C*', map { rand 256 } 1 .. $size; +} diff --git a/src/test/resources/module/Crypt-Rijndael/t/rijndael.t b/src/test/resources/module/Crypt-Rijndael/t/rijndael.t new file mode 100644 index 0000000000..91d83a5c3b --- /dev/null +++ b/src/test/resources/module/Crypt-Rijndael/t/rijndael.t @@ -0,0 +1,35 @@ +use strict; +use warnings; +use Test::More; +use Crypt::Rijndael; + +is(Crypt::Rijndael->keysize, 32, 'keysize'); +is(Crypt::Rijndael->blocksize, 16, 'blocksize'); + +my $plain = pack 'H*', '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710'; +my $iv = pack 'H*', '000102030405060708090a0b0c0d0e0f'; +my @vectors = ( + [ MODE_ECB => '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4', undef, 'f3eed1bdb5d2a03c064b5a7e3db181f8591ccb10d410ed26dc5ba74a31362870b6ed21b99ca6f4f9f153e7b1beafed1d23304b7a39f9f3ff067d8d8f9e24ecc7' ], + [ MODE_CBC => '2b7e151628aed2a6abf7158809cf4f3c', $iv, '7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7' ], + [ MODE_CFB => '2b7e151628aed2a6abf7158809cf4f3c', $iv, '3b3fd92eb72dad20333449f8e83cfb4ac8a64537a0b3a93fcde3cdad9f1ce58b26751f67a3cbb140b1808cf187a4f4dfc04b05357c5d1c0eeac4c66f9ff7f2e6' ], + [ MODE_OFB => '2b7e151628aed2a6abf7158809cf4f3c', $iv, '3b3fd92eb72dad20333449f8e83cfb4a7789508d16918f03f53c52dac54ed8259740051e9c5fecf64344f7a82260edcc304c6528f659c77866a510d9c1d6ae5e' ], + [ MODE_CTR => '2b7e151628aed2a6abf7158809cf4f3c', pack('H*', 'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'), '874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee' ], +); + +for my $vector (@vectors) { + my ($constant, $key_hex, $vector_iv, $cipher_hex) = @$vector; + my $mode = Crypt::Rijndael->$constant(); + my $cipher = Crypt::Rijndael->new(pack('H*', $key_hex), $mode); + $cipher->set_iv($vector_iv) if defined $vector_iv; + my $encrypted = $cipher->encrypt($plain); + is unpack('H*', $encrypted), $cipher_hex, "$constant NIST vector"; + is $cipher->decrypt($encrypted), $plain, "$constant decrypts"; +} + +my $partial = 'partial CFB/OFB/CTR data'; +for my $mode (Crypt::Rijndael::MODE_CFB(), Crypt::Rijndael::MODE_OFB(), Crypt::Rijndael::MODE_CTR()) { + my $cipher = Crypt::Rijndael->new('k' x 16, $mode); + is $cipher->decrypt($cipher->encrypt($partial, $iv), $iv), $partial, "mode $mode supports partial blocks"; +} + +done_testing; diff --git a/src/test/resources/module/Crypt-Rijndael/t/rt/27632.t b/src/test/resources/module/Crypt-Rijndael/t/rt/27632.t new file mode 100644 index 0000000000..d8b15855df --- /dev/null +++ b/src/test/resources/module/Crypt-Rijndael/t/rt/27632.t @@ -0,0 +1,43 @@ +#!/usr/bin/perl + +use strict; +use Crypt::Rijndael; +use Digest::MD5 qw(md5_hex); + +use Test::More 'no_plan'; + +my $class = 'Crypt::Rijndael'; + +my $key = 'abcdefghijklmnop'; + +my $in_plain = 'a' x 32; + +my $cipher = $class->new( $key, Crypt::Rijndael::MODE_CBC ); +isa_ok( $cipher, $class ); + +$cipher->set_iv('a' x 16); + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# encrypt +diag( "-" x 50 ) if $ENV{DEBUG}; + +my $crypt = $cipher->encrypt( $in_plain ); + +diag( "Plain text: [$in_plain]" ) if $ENV{DEBUG}; +diag( "Crypt text: [$crypt]" ) if $ENV{DEBUG}; + +my $digest = md5_hex( $crypt ); +diag( "MD5 digest of crypt: [$digest]" ) if $ENV{DEBUG}; + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# decrypt to see if we get back the same thing +{ +diag( "-" x 50 ) if $ENV{DEBUG}; + +my $out_plain = $cipher->decrypt( $crypt ); + +diag( "Crypt text: [$crypt]" ) if $ENV{DEBUG}; +diag( "Plain text: [$out_plain]" ) if $ENV{DEBUG}; + +is( $out_plain, $in_plain, "Text comes back correctly" ); +}