From de1e4a452189801259bcd0778d007010d52fe4a7 Mon Sep 17 00:00:00 2001
From: Umar Hayat <postgresql.wizard@gmail.com>
Date: Wed, 29 Jan 2025 18:49:19 +0900
Subject: [PATCH] pgcrypto: add AES encryption CFB mode support

---
 contrib/pgcrypto/expected/rijndael.out | 118 +++++++++++++++++++++++++
 contrib/pgcrypto/openssl.c             |  39 ++++++++
 contrib/pgcrypto/sql/rijndael.sql      |  53 +++++++++++
 doc/src/sgml/pgcrypto.sgml             |   5 ++
 4 files changed, 215 insertions(+)

diff --git a/contrib/pgcrypto/expected/rijndael.out b/contrib/pgcrypto/expected/rijndael.out
index 015ba4430d9..28e28b989fd 100644
--- a/contrib/pgcrypto/expected/rijndael.out
+++ b/contrib/pgcrypto/expected/rijndael.out
@@ -135,3 +135,121 @@ select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'aes')
  Lets try a longer message.
 (1 row)
 
+-- cfb
+SELECT encrypt(
+'\x00112233445566778899aabbccddeeff',
+'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+'aes-cfb/pad:none');
+              encrypt               
+------------------------------------
+ \xf28122856e1cf9a7216a30d111f3997f
+(1 row)
+
+-- without padding, input not multiple of block size
+SELECT encrypt(
+'\x00112233445566778899aabbccddeeff00',
+'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+'aes-cfb/pad:none');
+               encrypt                
+--------------------------------------
+ \xf28122856e1cf9a7216a30d111f3997fcb
+(1 row)
+
+-- key padding
+SELECT encrypt(
+'\x0011223344',
+'\x000102030405',
+'aes-cfb');
+   encrypt    
+--------------
+ \x8145d1a0ef
+(1 row)
+
+SELECT encrypt(
+'\x0011223344',
+'\x000102030405060708090a0b0c0d0e0f10111213',
+'aes-cfb');
+   encrypt    
+--------------
+ \x52642c3b9c
+(1 row)
+
+SELECT encrypt(
+'\x0011223344',
+'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b',
+'aes-cfb');
+   encrypt    
+--------------
+ \xc93b4468a4
+(1 row)
+
+-- empty data
+select encrypt('', 'foo', 'aes-cfb');
+ encrypt 
+---------
+ \x
+(1 row)
+
+-- 10 bytes key
+select encrypt('foo', '0123456789', 'aes-cfb');
+ encrypt  
+----------
+ \x6f8ced
+(1 row)
+
+-- 22 bytes key
+select encrypt('foo', '0123456789012345678901', 'aes-cfb');
+ encrypt  
+----------
+ \xfb47d2
+(1 row)
+
+-- decrypt
+select encode(decrypt(encrypt('foo', '0123456', 'aes-cfb'), '0123456', 'aes-cfb'), 'escape');
+ encode 
+--------
+ foo
+(1 row)
+
+-- data not multiple of block size
+select encode(decrypt(encrypt('foo', '0123456', 'aes-cfb') || '\x00'::bytea, '0123456', 'aes-cfb'), 'escape');
+ encode  
+---------
+ foo\337
+(1 row)
+
+-- bad padding
+-- (The input value is the result of encrypt_iv('abcdefghijklmnopqrstuvwxyz', '0123456', 'abcd', 'aes-cfb')
+-- with the 16th byte changed (s/c5/d5/) to corrupt the padding of the last block.)
+select encode(decrypt_iv('\xf9ad6817cb58d31dd9ba6571fbc4f55d56f65b631f0f437cb828', '0123456', 'abcd', 'aes-cfb'), 'escape');
+                     encode                      
+-------------------------------------------------
+ abcdefghijklmnoq\243:\205o\x7F\x05z\276\x07\332
+(1 row)
+
+-- iv
+select encrypt_iv('foo', '0123456', 'abcd', 'aes-cfb');
+ encrypt_iv 
+------------
+ \xfea064
+(1 row)
+
+select encode(decrypt_iv('\xfea064', '0123456', 'abcd', 'aes-cfb'), 'escape');
+ encode 
+--------
+ foo
+(1 row)
+
+-- long message
+select encrypt('Lets try a longer message.', '0123456789', 'aes-cfb');
+                        encrypt                         
+--------------------------------------------------------
+ \x4586f6c6351055051f723b1b0aad52c877eaf0c421d18fd73a28
+(1 row)
+
+select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'aes-cfb'), '0123456789', 'aes-cfb'), 'escape');
+           encode           
+----------------------------
+ Lets try a longer message.
+(1 row)
+
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index 75f40a2d034..f179e80c842 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -617,6 +617,36 @@ ossl_aes_cbc_init(PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv
 	return err;
 }
 
+static int
+ossl_aes_cfb_init(PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+	OSSLCipher *od = c->ptr;
+	int			err;
+
+	err = ossl_aes_init(c, key, klen, iv);
+	if (err)
+		return err;
+
+	switch (od->klen)
+	{
+		case 128 / 8:
+			od->evp_ciph = EVP_aes_128_cfb();
+			break;
+		case 192 / 8:
+			od->evp_ciph = EVP_aes_192_cfb();
+			break;
+		case 256 / 8:
+			od->evp_ciph = EVP_aes_256_cfb();
+			break;
+		default:
+			/* shouldn't happen */
+			err = PXE_CIPHER_INIT;
+			break;
+	}
+
+	return err;
+}
+
 /*
  * aliases
  */
@@ -636,6 +666,7 @@ static PX_Alias ossl_aliases[] = {
 	{"rijndael", "aes-cbc"},
 	{"rijndael-cbc", "aes-cbc"},
 	{"rijndael-ecb", "aes-ecb"},
+	{"rijndael-cfb", "aes-cfb"},
 	{NULL}
 };
 
@@ -707,6 +738,13 @@ static const struct ossl_cipher ossl_aes_cbc = {
 	128 / 8, 256 / 8
 };
 
+static const struct ossl_cipher ossl_aes_cfb = {
+	ossl_aes_cfb_init,
+	NULL,						/* EVP_aes_XXX_cfb(), determined in init
+								 * function */
+	128 / 8, 256 / 8
+};
+
 /*
  * Special handlers
  */
@@ -728,6 +766,7 @@ static const struct ossl_cipher_lookup ossl_cipher_types[] = {
 	{"cast5-cbc", &ossl_cast_cbc},
 	{"aes-ecb", &ossl_aes_ecb},
 	{"aes-cbc", &ossl_aes_cbc},
+	{"aes-cfb", &ossl_aes_cfb},
 	{NULL}
 };
 
diff --git a/contrib/pgcrypto/sql/rijndael.sql b/contrib/pgcrypto/sql/rijndael.sql
index a2766419980..de4e4da5e29 100644
--- a/contrib/pgcrypto/sql/rijndael.sql
+++ b/contrib/pgcrypto/sql/rijndael.sql
@@ -70,3 +70,56 @@ select encode(decrypt_iv('\x2c24cb7da91d6d5699801268b0f5adad', '0123456', 'abcd'
 -- long message
 select encrypt('Lets try a longer message.', '0123456789', 'aes');
 select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'aes'), '0123456789', 'aes'), 'escape');
+
+-- cfb
+SELECT encrypt(
+'\x00112233445566778899aabbccddeeff',
+'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+'aes-cfb/pad:none');
+
+-- without padding, input not multiple of block size
+SELECT encrypt(
+'\x00112233445566778899aabbccddeeff00',
+'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+'aes-cfb/pad:none');
+
+-- key padding
+
+SELECT encrypt(
+'\x0011223344',
+'\x000102030405',
+'aes-cfb');
+
+SELECT encrypt(
+'\x0011223344',
+'\x000102030405060708090a0b0c0d0e0f10111213',
+'aes-cfb');
+
+SELECT encrypt(
+'\x0011223344',
+'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b',
+'aes-cfb');
+
+-- empty data
+select encrypt('', 'foo', 'aes-cfb');
+-- 10 bytes key
+select encrypt('foo', '0123456789', 'aes-cfb');
+-- 22 bytes key
+select encrypt('foo', '0123456789012345678901', 'aes-cfb');
+
+-- decrypt
+select encode(decrypt(encrypt('foo', '0123456', 'aes-cfb'), '0123456', 'aes-cfb'), 'escape');
+-- data not multiple of block size
+select encode(decrypt(encrypt('foo', '0123456', 'aes-cfb') || '\x00'::bytea, '0123456', 'aes-cfb'), 'escape');
+-- bad padding
+-- (The input value is the result of encrypt_iv('abcdefghijklmnopqrstuvwxyz', '0123456', 'abcd', 'aes-cfb')
+-- with the 16th byte changed (s/c5/d5/) to corrupt the padding of the last block.)
+select encode(decrypt_iv('\xf9ad6817cb58d31dd9ba6571fbc4f55d56f65b631f0f437cb828', '0123456', 'abcd', 'aes-cfb'), 'escape');
+
+-- iv
+select encrypt_iv('foo', '0123456', 'abcd', 'aes-cfb');
+select encode(decrypt_iv('\xfea064', '0123456', 'abcd', 'aes-cfb'), 'escape');
+
+-- long message
+select encrypt('Lets try a longer message.', '0123456789', 'aes-cfb');
+select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'aes-cfb'), '0123456789', 'aes-cfb'), 'escape');
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index a4d035eabdd..dc677442939 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -1082,6 +1082,11 @@ decrypt_iv(data bytea, key bytea, iv bytea, type text) returns bytea
     <literal>cbc</literal> &mdash; next block depends on previous (default)
     </para>
    </listitem>
+   <listitem>
+    <para>
+    <literal>cfb</literal> &mdash; next block depends on previous encrypted block
+    </para>
+   </listitem>
    <listitem>
     <para>
     <literal>ecb</literal> &mdash; each block is encrypted separately (for
-- 
2.31.0

