Encoding protection for pgcrypto

Started by shihao zhongalmost 2 years ago3 messages
#1shihao zhong
zhong950419@gmail.com
1 attachment(s)

Hi hackers,

Currently, pgp_sym_decrypt_text and pgp_pub_decrypt_text doesn't
enforce database encoding validation even when returning text. This
patch adds validation and dedicated tests to verify its
effectiveness. Additionally, some existing unit tests were moved to
the new tests as they failed in some encoding.

Thanks,
SHihao

Attachments:

fix_pycrypto.patchapplication/octet-stream; name=fix_pycrypto.patchDownload
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 5efa10c334..2e59cdee9b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -42,7 +42,7 @@ PGFILEDESC = "pgcrypto - cryptographic functions"
 REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
 	sha2 des 3des cast5 \
 	crypt-des crypt-md5 crypt-blowfish crypt-xdes \
-	pgp-armor pgp-decrypt pgp-encrypt pgp-encrypt-md5 $(CF_PGP_TESTS) \
+	pgp-armor pgp-decrypt pgp-decrypt_utf8 pgp-encrypt pgp-encrypt-md5 $(CF_PGP_TESTS) \
 	pgp-pubkey-decrypt pgp-pubkey-encrypt pgp-info
 
 EXTRA_CLEAN = gen-rtab
diff --git a/contrib/pgcrypto/expected/pgp-decrypt.out b/contrib/pgcrypto/expected/pgp-decrypt.out
index eb049ba9d4..51416bfa39 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt.out
@@ -315,21 +315,6 @@ SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
  \xda39a3ee5e6b4b0d3255bfef95601890afd80709
 (1 row)
 
-select digest(pgp_sym_decrypt(dearmor('
------BEGIN PGP MESSAGE-----
-Comment: dat3.aes.sha1.mdc.s2k3.z0
-
-jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
-gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
-73Hb8m1yRhQK
-=ivrD
------END PGP MESSAGE-----
-'), '0123456789abcdefghij'), 'sha1');
-                   digest                   
---------------------------------------------
- \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
-(1 row)
-
 -- Checking CRLF
 select digest(pgp_sym_decrypt(dearmor('
 -----BEGIN PGP MESSAGE-----
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_1.out b/contrib/pgcrypto/expected/pgp-decrypt_1.out
index 80a4c48613..c8b603b6f6 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt_1.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt_1.out
@@ -311,21 +311,6 @@ SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
  \xda39a3ee5e6b4b0d3255bfef95601890afd80709
 (1 row)
 
-select digest(pgp_sym_decrypt(dearmor('
------BEGIN PGP MESSAGE-----
-Comment: dat3.aes.sha1.mdc.s2k3.z0
-
-jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
-gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
-73Hb8m1yRhQK
-=ivrD
------END PGP MESSAGE-----
-'), '0123456789abcdefghij'), 'sha1');
-                   digest                   
---------------------------------------------
- \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
-(1 row)
-
 -- Checking CRLF
 select digest(pgp_sym_decrypt(dearmor('
 -----BEGIN PGP MESSAGE-----
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_utf8.out b/contrib/pgcrypto/expected/pgp-decrypt_utf8.out
new file mode 100644
index 0000000000..136bef970a
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-decrypt_utf8.out
@@ -0,0 +1,27 @@
+--
+-- pgp decrypt tests with utf8 encoding
+--
+select getdatabaseencoding() = 'UTF8' AS encoding_test \gset
+\if :encoding_test
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+-- Database encoding protection.  Ciphertext source:
+--  printf '\xe0\xe0\xbff' | gpg --batch --passphrase mykey --textmode --armor --symmetric
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0ECQMIk4NUq+Ia+pb+0jkBaKWe8y03NKddkoMTFZt467Qc+JEp4zdlqTdA4d0d
+Pr66ILfi67O8N4AByEjIyeR2ZPvrKN+xdSo=
+=QKy4
+-----END PGP MESSAGE-----
+'), 'mykey', 'debug=1');
+\quit
+\endif
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_utf8_1.out b/contrib/pgcrypto/expected/pgp-decrypt_utf8_1.out
new file mode 100644
index 0000000000..9bc927be63
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-decrypt_utf8_1.out
@@ -0,0 +1,27 @@
+--
+-- pgp decrypt tests with utf8 encoding
+--
+select getdatabaseencoding() = 'UTF8' AS encoding_test \gset
+\if :encoding_test
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+-- Database encoding protection.  Ciphertext source:
+-- printf '\xe0\xe0\xbff' | gpg --batch --passphrase mykey --textmode --armor --symmetric
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0ECQMIk4NUq+Ia+pb+0jkBaKWe8y03NKddkoMTFZt467Qc+JEp4zdlqTdA4d0d
+Pr66ILfi67O8N4AByEjIyeR2ZPvrKN+xdSo=
+=QKy4
+-----END PGP MESSAGE-----
+'), 'mykey', 'debug=1');
+\quit
+\endif
diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build
index 8594891548..60030ad124 100644
--- a/contrib/pgcrypto/meson.build
+++ b/contrib/pgcrypto/meson.build
@@ -47,6 +47,7 @@ pgcrypto_regress = [
   'crypt-xdes',
   'pgp-armor',
   'pgp-decrypt',
+  'pgp-decrypt_utf8',
   'pgp-encrypt',
   'pgp-encrypt-md5',
   'pgp-pubkey-decrypt',
diff --git a/contrib/pgcrypto/pgp-pgsql.c b/contrib/pgcrypto/pgp-pgsql.c
index d9b15b07b0..838a7c381f 100644
--- a/contrib/pgcrypto/pgp-pgsql.c
+++ b/contrib/pgcrypto/pgp-pgsql.c
@@ -631,6 +631,7 @@ pgp_sym_decrypt_text(PG_FUNCTION_ARGS)
 		arg = PG_GETARG_BYTEA_PP(2);
 
 	res = decrypt_internal(0, 1, data, key, NULL, arg);
+	pg_verifymbstr(VARDATA_ANY(res), VARSIZE_ANY_EXHDR(res), false);
 
 	PG_FREE_IF_COPY(data, 0);
 	PG_FREE_IF_COPY(key, 1);
@@ -732,6 +733,7 @@ pgp_pub_decrypt_text(PG_FUNCTION_ARGS)
 		arg = PG_GETARG_BYTEA_PP(3);
 
 	res = decrypt_internal(1, 1, data, key, psw, arg);
+	pg_verifymbstr(VARDATA_ANY(res), VARSIZE_ANY_EXHDR(res), false);
 
 	PG_FREE_IF_COPY(data, 0);
 	PG_FREE_IF_COPY(key, 1);
diff --git a/contrib/pgcrypto/sql/pgp-decrypt.sql b/contrib/pgcrypto/sql/pgp-decrypt.sql
index 49a0267bbc..608d5a31c2 100644
--- a/contrib/pgcrypto/sql/pgp-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-decrypt.sql
@@ -228,17 +228,6 @@ SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
 -----END PGP MESSAGE-----
 '), '0123456789abcdefghij'), 'sha1');
 
-select digest(pgp_sym_decrypt(dearmor('
------BEGIN PGP MESSAGE-----
-Comment: dat3.aes.sha1.mdc.s2k3.z0
-
-jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
-gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
-73Hb8m1yRhQK
-=ivrD
------END PGP MESSAGE-----
-'), '0123456789abcdefghij'), 'sha1');
-
 -- Checking CRLF
 select digest(pgp_sym_decrypt(dearmor('
 -----BEGIN PGP MESSAGE-----
diff --git a/contrib/pgcrypto/sql/pgp-decrypt_utf8.sql b/contrib/pgcrypto/sql/pgp-decrypt_utf8.sql
new file mode 100644
index 0000000000..4df93a44db
--- /dev/null
+++ b/contrib/pgcrypto/sql/pgp-decrypt_utf8.sql
@@ -0,0 +1,29 @@
+--
+-- pgp decrypt tests with utf8 encoding
+--
+select getdatabaseencoding() = 'UTF8' AS encoding_test \gset
+\if :encoding_test
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+
+-- Database encoding protection.  Ciphertext source:
+-- printf '\xe0\xe0\xbff' | gpg --batch --passphrase mykey --textmode --armor --symmetric
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0ECQMIk4NUq+Ia+pb+0jkBaKWe8y03NKddkoMTFZt467Qc+JEp4zdlqTdA4d0d
+Pr66ILfi67O8N4AByEjIyeR2ZPvrKN+xdSo=
+=QKy4
+-----END PGP MESSAGE-----
+'), 'mykey', 'debug=1');
+\quit
+\endif
#2cary huang
hcary328@gmail.com
In reply to: shihao zhong (#1)
Re: Encoding protection for pgcrypto

The following review has been posted through the commitfest application:
make installcheck-world: tested, failed
Implements feature: not tested
Spec compliant: not tested
Documentation: not tested

Hello

I had a look at your patch, which applies fine to PostgreSQL master. I noticed that the new regression tests you have added to test utf-8 encoding fails on my setup (make check) with the following diffs:

---------------------------------------
@ -13,6 +13,7 @@
 =ivrD
 -----END PGP MESSAGE-----
 '), '0123456789abcdefghij'), 'sha1');
+ERROR:  invalid byte sequence for encoding "UTF8": 0x91
 -- Database encoding protection.  Ciphertext source:
 -- printf '\xe0\xe0\xbff' | gpg --batch --passphrase mykey --textmode --armor --symmetric
 select pgp_sym_decrypt(dearmor('
@@ -23,5 +24,5 @@
 =QKy4
 -----END PGP MESSAGE-----
 '), 'mykey', 'debug=1');
+ERROR:  invalid byte sequence for encoding "UTF8": 0xe0 0xe0 0xbf
 \quit
-\endif
---------------------------------------

I am not sure but it seems that you intentionally provide a text input that would produce a non-utf-8 compliant decrypted output, which triggers the error from within "pg_verifymbstr()" call that you have added in pgp-pgsql.c? Are the errors expected in your new test case? If so, then the tests shall pass instead because it has caught a invalid encoding in decrypted output.

Generally, I am ok with the extra encoding check after text decryption but do not think if it is a good idea to just error out and abort the transaction when it detects invalid encoding character. text decryption routines may be used quite frequently and users generally do not expect them to abort transaction. It may be ok to just give them a warning about invalid character encoding.

thanks
--------------------
Cary Huang
Highgo Software - Canada
www.highgo.ca

#3shihao zhong
zhong950419@gmail.com
In reply to: cary huang (#2)
1 attachment(s)
Re: Encoding protection for pgcrypto

On Fri, Feb 9, 2024 at 5:34 PM cary huang <hcary328@gmail.com> wrote:

The following review has been posted through the commitfest application:
make installcheck-world: tested, failed
Implements feature: not tested
Spec compliant: not tested
Documentation: not tested

Hello

I had a look at your patch, which applies fine to PostgreSQL master. I noticed that the new regression tests you have added to test utf-8 encoding fails on my setup (make check) with the following diffs:

---------------------------------------
@ -13,6 +13,7 @@
=ivrD
-----END PGP MESSAGE-----
'), '0123456789abcdefghij'), 'sha1');
+ERROR:  invalid byte sequence for encoding "UTF8": 0x91
-- Database encoding protection.  Ciphertext source:
-- printf '\xe0\xe0\xbff' | gpg --batch --passphrase mykey --textmode --armor --symmetric
select pgp_sym_decrypt(dearmor('
@@ -23,5 +24,5 @@
=QKy4
-----END PGP MESSAGE-----
'), 'mykey', 'debug=1');
+ERROR:  invalid byte sequence for encoding "UTF8": 0xe0 0xe0 0xbf
\quit
-\endif
---------------------------------------

I am not sure but it seems that you intentionally provide a text input that would produce a non-utf-8 compliant decrypted output, which triggers the error from within "pg_verifymbstr()" call that you have added in pgp-pgsql.c? Are the errors expected in your new test case? If so, then the tests shall pass instead because it has caught a invalid encoding in decrypted output.

Thanks for sharing that, I had updated the pgp-decrypt_utf8.out in the
v2.patch which will pass the `make -C contrib/pgcrypto check`.

Generally, I am ok with the extra encoding check after text decryption but do not think if it is a good idea to just error out and abort the transaction when it detects invalid encoding character. text decryption routines may be used quite frequently and users generally do not expect them to abort transaction. It may be ok to just give them a warning about invalid character encoding.

Thanks for pointing that out. The goal for this patch is to fix the
encoding for the TEXT return value because by default the PostgreSQL
TEXT type should have the same encoding as the database encoding. So I
only added mbverify for the pgp_sym_decrypt_text and
pgp_pub_decrypt_text functions. If customers want to use these two
functions without encoding, they should use pgp_pub_decrypt_bytea and
pgp_sym_decrypt_bytea because BYTEA is represented as a binary string
in PostgreSQL.

Please let me know if you have more questions or concerns. Thanks!

Show quoted text

thanks
--------------------
Cary Huang
Highgo Software - Canada
www.highgo.ca

Attachments:

fix_pycrypto_v2.patchapplication/octet-stream; name=fix_pycrypto_v2.patchDownload
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 5efa10c334..2e59cdee9b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -42,7 +42,7 @@ PGFILEDESC = "pgcrypto - cryptographic functions"
 REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
 	sha2 des 3des cast5 \
 	crypt-des crypt-md5 crypt-blowfish crypt-xdes \
-	pgp-armor pgp-decrypt pgp-encrypt pgp-encrypt-md5 $(CF_PGP_TESTS) \
+	pgp-armor pgp-decrypt pgp-decrypt_utf8 pgp-encrypt pgp-encrypt-md5 $(CF_PGP_TESTS) \
 	pgp-pubkey-decrypt pgp-pubkey-encrypt pgp-info
 
 EXTRA_CLEAN = gen-rtab
diff --git a/contrib/pgcrypto/expected/pgp-decrypt.out b/contrib/pgcrypto/expected/pgp-decrypt.out
index eb049ba9d4..51416bfa39 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt.out
@@ -315,21 +315,6 @@ SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
  \xda39a3ee5e6b4b0d3255bfef95601890afd80709
 (1 row)
 
-select digest(pgp_sym_decrypt(dearmor('
------BEGIN PGP MESSAGE-----
-Comment: dat3.aes.sha1.mdc.s2k3.z0
-
-jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
-gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
-73Hb8m1yRhQK
-=ivrD
------END PGP MESSAGE-----
-'), '0123456789abcdefghij'), 'sha1');
-                   digest                   
---------------------------------------------
- \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
-(1 row)
-
 -- Checking CRLF
 select digest(pgp_sym_decrypt(dearmor('
 -----BEGIN PGP MESSAGE-----
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_1.out b/contrib/pgcrypto/expected/pgp-decrypt_1.out
index 80a4c48613..c8b603b6f6 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt_1.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt_1.out
@@ -311,21 +311,6 @@ SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
  \xda39a3ee5e6b4b0d3255bfef95601890afd80709
 (1 row)
 
-select digest(pgp_sym_decrypt(dearmor('
------BEGIN PGP MESSAGE-----
-Comment: dat3.aes.sha1.mdc.s2k3.z0
-
-jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
-gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
-73Hb8m1yRhQK
-=ivrD
------END PGP MESSAGE-----
-'), '0123456789abcdefghij'), 'sha1');
-                   digest                   
---------------------------------------------
- \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
-(1 row)
-
 -- Checking CRLF
 select digest(pgp_sym_decrypt(dearmor('
 -----BEGIN PGP MESSAGE-----
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_utf8.out b/contrib/pgcrypto/expected/pgp-decrypt_utf8.out
new file mode 100644
index 0000000000..c3d2935a9d
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-decrypt_utf8.out
@@ -0,0 +1,28 @@
+--
+-- pgp decrypt tests with utf8 encoding
+--
+select getdatabaseencoding() = 'UTF8' AS encoding_test \gset
+\if :encoding_test
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ERROR:  invalid byte sequence for encoding "UTF8": 0x91
+-- Database encoding protection.  Ciphertext source:
+-- printf '\xe0\xe0\xbff' | gpg --batch --passphrase mykey --textmode --armor --symmetric
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0ECQMIk4NUq+Ia+pb+0jkBaKWe8y03NKddkoMTFZt467Qc+JEp4zdlqTdA4d0d
+Pr66ILfi67O8N4AByEjIyeR2ZPvrKN+xdSo=
+=QKy4
+-----END PGP MESSAGE-----
+'), 'mykey', 'debug=1');
+ERROR:  invalid byte sequence for encoding "UTF8": 0xe0 0xe0 0xbf
+\quit
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_utf8_1.out b/contrib/pgcrypto/expected/pgp-decrypt_utf8_1.out
new file mode 100644
index 0000000000..9bc927be63
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-decrypt_utf8_1.out
@@ -0,0 +1,27 @@
+--
+-- pgp decrypt tests with utf8 encoding
+--
+select getdatabaseencoding() = 'UTF8' AS encoding_test \gset
+\if :encoding_test
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+-- Database encoding protection.  Ciphertext source:
+-- printf '\xe0\xe0\xbff' | gpg --batch --passphrase mykey --textmode --armor --symmetric
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0ECQMIk4NUq+Ia+pb+0jkBaKWe8y03NKddkoMTFZt467Qc+JEp4zdlqTdA4d0d
+Pr66ILfi67O8N4AByEjIyeR2ZPvrKN+xdSo=
+=QKy4
+-----END PGP MESSAGE-----
+'), 'mykey', 'debug=1');
+\quit
+\endif
diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build
index 8594891548..60030ad124 100644
--- a/contrib/pgcrypto/meson.build
+++ b/contrib/pgcrypto/meson.build
@@ -47,6 +47,7 @@ pgcrypto_regress = [
   'crypt-xdes',
   'pgp-armor',
   'pgp-decrypt',
+  'pgp-decrypt_utf8',
   'pgp-encrypt',
   'pgp-encrypt-md5',
   'pgp-pubkey-decrypt',
diff --git a/contrib/pgcrypto/pgp-pgsql.c b/contrib/pgcrypto/pgp-pgsql.c
index d9b15b07b0..838a7c381f 100644
--- a/contrib/pgcrypto/pgp-pgsql.c
+++ b/contrib/pgcrypto/pgp-pgsql.c
@@ -631,6 +631,7 @@ pgp_sym_decrypt_text(PG_FUNCTION_ARGS)
 		arg = PG_GETARG_BYTEA_PP(2);
 
 	res = decrypt_internal(0, 1, data, key, NULL, arg);
+	pg_verifymbstr(VARDATA_ANY(res), VARSIZE_ANY_EXHDR(res), false);
 
 	PG_FREE_IF_COPY(data, 0);
 	PG_FREE_IF_COPY(key, 1);
@@ -732,6 +733,7 @@ pgp_pub_decrypt_text(PG_FUNCTION_ARGS)
 		arg = PG_GETARG_BYTEA_PP(3);
 
 	res = decrypt_internal(1, 1, data, key, psw, arg);
+	pg_verifymbstr(VARDATA_ANY(res), VARSIZE_ANY_EXHDR(res), false);
 
 	PG_FREE_IF_COPY(data, 0);
 	PG_FREE_IF_COPY(key, 1);
diff --git a/contrib/pgcrypto/sql/pgp-decrypt.sql b/contrib/pgcrypto/sql/pgp-decrypt.sql
index 49a0267bbc..608d5a31c2 100644
--- a/contrib/pgcrypto/sql/pgp-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-decrypt.sql
@@ -228,17 +228,6 @@ SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
 -----END PGP MESSAGE-----
 '), '0123456789abcdefghij'), 'sha1');
 
-select digest(pgp_sym_decrypt(dearmor('
------BEGIN PGP MESSAGE-----
-Comment: dat3.aes.sha1.mdc.s2k3.z0
-
-jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
-gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
-73Hb8m1yRhQK
-=ivrD
------END PGP MESSAGE-----
-'), '0123456789abcdefghij'), 'sha1');
-
 -- Checking CRLF
 select digest(pgp_sym_decrypt(dearmor('
 -----BEGIN PGP MESSAGE-----
diff --git a/contrib/pgcrypto/sql/pgp-decrypt_utf8.sql b/contrib/pgcrypto/sql/pgp-decrypt_utf8.sql
new file mode 100644
index 0000000000..4df93a44db
--- /dev/null
+++ b/contrib/pgcrypto/sql/pgp-decrypt_utf8.sql
@@ -0,0 +1,29 @@
+--
+-- pgp decrypt tests with utf8 encoding
+--
+select getdatabaseencoding() = 'UTF8' AS encoding_test \gset
+\if :encoding_test
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+
+-- Database encoding protection.  Ciphertext source:
+-- printf '\xe0\xe0\xbff' | gpg --batch --passphrase mykey --textmode --armor --symmetric
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0ECQMIk4NUq+Ia+pb+0jkBaKWe8y03NKddkoMTFZt467Qc+JEp4zdlqTdA4d0d
+Pr66ILfi67O8N4AByEjIyeR2ZPvrKN+xdSo=
+=QKy4
+-----END PGP MESSAGE-----
+'), 'mykey', 'debug=1');
+\quit
+\endif