pgcrypto supports s2k-mode for key-stretching during symmetric
encryption, and even defaults to s2k-mode=3, which means configurable
iterations.  But it doesn't support s2k-count to actually set those
iterations to be anything other than the default.  If you are
interested in key-stretching, the default is not going to cut it.
(You could argue that pgp's s2k doesn't cut it either even at the max,
but at least we should offer the maximum that the pgp spec makes
available.)

This patch implements s2k-count as an option to pgp_sym_encrypt.

Demo (note the password is intentionally wrong in the last character):

select pgp_sym_decrypt(
    pgp_sym_encrypt('foobar','acf86729b6b0289f4d1909db8c1aaf0c','s2k-mode=3'),
    'acf86729b6b0289f4d1909db8c1aaf0d');
ERROR:  Wrong key or corrupt data
Time: 1.606 ms

select pgp_sym_decrypt(
   
pgp_sym_encrypt('foobar','acf86729b6b0289f4d1909db8c1aaf0c','s2k-mode=3,s2k-count=65000000'),
   'acf86729b6b0289f4d1909db8c1aaf0d');
ERROR:  Wrong key or corrupt data
Time: 615.720 ms

I did not bump the extension version.  I realized the migration file
would be empty, as there no change to SQL-level functionality (the new
s2k-count is parsed out of a string down in the C code).  Since only
one version of contrib extensions binary object files are installed in
any given postgres installation, people using the newer binary gets
the feature even if they have not updated the extension version.  So I
don't know if it makes sense to bump the version if people inherently
get the feature anyway.

Cheers,

Jeff
diff --git a/contrib/pgcrypto/expected/pgp-encrypt.out b/contrib/pgcrypto/expected/pgp-encrypt.out
new file mode 100644
index b35de79..2bf999f
*** a/contrib/pgcrypto/expected/pgp-encrypt.out
--- b/contrib/pgcrypto/expected/pgp-encrypt.out
*************** select pgp_sym_decrypt(
*** 103,108 ****
--- 103,126 ----
   Secret.
  (1 row)
  
+ -- s2k count change
+ select pgp_sym_decrypt(
+ 	pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
+ 	'key', 'expect-s2k-count=1024');
+  pgp_sym_decrypt 
+ -----------------
+  Secret.
+ (1 row)
+ 
+ select pgp_sym_decrypt(
+ 	pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
+ 	'key', 'expect-s2k-count=65000000'); -- rounded up
+ NOTICE:  pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712
+  pgp_sym_decrypt 
+ -----------------
+  Secret.
+ (1 row)
+ 
  -- s2k digest change
  select pgp_sym_decrypt(
  	pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'),
diff --git a/contrib/pgcrypto/pgp-decrypt.c b/contrib/pgcrypto/pgp-decrypt.c
new file mode 100644
index 5c69745..3d16033
*** a/contrib/pgcrypto/pgp-decrypt.c
--- b/contrib/pgcrypto/pgp-decrypt.c
*************** parse_symenc_sesskey(PGP_Context *ctx, P
*** 643,648 ****
--- 643,649 ----
  	if (res < 0)
  		return res;
  	ctx->s2k_mode = ctx->s2k.mode;
+ 	ctx->s2k_count = iter_to_count(ctx->s2k.iter);
  	ctx->s2k_digest_algo = ctx->s2k.digest_algo;
  
  	/*
diff --git a/contrib/pgcrypto/pgp-encrypt.c b/contrib/pgcrypto/pgp-encrypt.c
new file mode 100644
index 2320c75..c9148fd
*** a/contrib/pgcrypto/pgp-encrypt.c
--- b/contrib/pgcrypto/pgp-encrypt.c
*************** init_s2k_key(PGP_Context *ctx)
*** 567,573 ****
  	if (ctx->s2k_cipher_algo < 0)
  		ctx->s2k_cipher_algo = ctx->cipher_algo;
  
! 	res = pgp_s2k_fill(&ctx->s2k, ctx->s2k_mode, ctx->s2k_digest_algo);
  	if (res < 0)
  		return res;
  
--- 567,573 ----
  	if (ctx->s2k_cipher_algo < 0)
  		ctx->s2k_cipher_algo = ctx->cipher_algo;
  
! 	res = pgp_s2k_fill(&ctx->s2k, ctx->s2k_mode, ctx->s2k_digest_algo, ctx->s2k_count);
  	if (res < 0)
  		return res;
  
diff --git a/contrib/pgcrypto/pgp-pgsql.c b/contrib/pgcrypto/pgp-pgsql.c
new file mode 100644
index 1842985..1b55bc9
*** a/contrib/pgcrypto/pgp-pgsql.c
--- b/contrib/pgcrypto/pgp-pgsql.c
*************** struct debug_expect
*** 181,186 ****
--- 181,187 ----
  	int			expect;
  	int			cipher_algo;
  	int			s2k_mode;
+ 	int			s2k_count;
  	int			s2k_cipher_algo;
  	int			s2k_digest_algo;
  	int			compress_algo;
*************** fill_expect(struct debug_expect * ex, in
*** 196,201 ****
--- 197,203 ----
  	ex->expect = 0;
  	ex->cipher_algo = -1;
  	ex->s2k_mode = -1;
+ 	ex->s2k_count= -1;
  	ex->s2k_cipher_algo = -1;
  	ex->s2k_digest_algo = -1;
  	ex->compress_algo = -1;
*************** check_expect(PGP_Context *ctx, struct de
*** 218,223 ****
--- 220,226 ----
  {
  	EX_CHECK(cipher_algo);
  	EX_CHECK(s2k_mode);
+ 	EX_CHECK(s2k_count);
  	EX_CHECK(s2k_digest_algo);
  	EX_CHECK(use_sess_key);
  	if (ctx->use_sess_key)
*************** set_arg(PGP_Context *ctx, char *key, cha
*** 247,252 ****
--- 250,257 ----
  		res = pgp_set_sess_key(ctx, atoi(val));
  	else if (strcmp(key, "s2k-mode") == 0)
  		res = pgp_set_s2k_mode(ctx, atoi(val));
+ 	else if (strcmp(key, "s2k-count") == 0)
+ 		res = pgp_set_s2k_count(ctx, atoi(val));
  	else if (strcmp(key, "s2k-digest-algo") == 0)
  		res = pgp_set_s2k_digest_algo(ctx, val);
  	else if (strcmp(key, "s2k-cipher-algo") == 0)
*************** set_arg(PGP_Context *ctx, char *key, cha
*** 286,291 ****
--- 291,301 ----
  		ex->expect = 1;
  		ex->s2k_mode = atoi(val);
  	}
+ 	else if (ex != NULL && strcmp(key, "expect-s2k-count") == 0)
+ 	{
+ 		ex->expect = 1;
+ 		ex->s2k_count = atoi(val);
+ 	}
  	else if (ex != NULL && strcmp(key, "expect-s2k-digest-algo") == 0)
  	{
  		ex->expect = 1;
diff --git a/contrib/pgcrypto/pgp-s2k.c b/contrib/pgcrypto/pgp-s2k.c
new file mode 100644
index 193dd95..baa8e88
*** a/contrib/pgcrypto/pgp-s2k.c
--- b/contrib/pgcrypto/pgp-s2k.c
*************** calc_s2k_iter_salted(PGP_S2K *s2k, PX_MD
*** 137,143 ****
  				count;
  
  	cval = s2k->iter;
! 	count = ((unsigned) 16 + (cval & 15)) << ((cval >> 4) + 6);
  
  	md_rlen = px_md_result_size(md);
  
--- 137,143 ----
  				count;
  
  	cval = s2k->iter;
! 	count = iter_to_count(cval);
  
  	md_rlen = px_md_result_size(md);
  
*************** calc_s2k_iter_salted(PGP_S2K *s2k, PX_MD
*** 200,215 ****
   * Too small: weak
   * Too big: slow
   * gpg defaults to 96 => 65536 iters
!  * let it float a bit: 96 + 32 => 262144 iters
   */
  static int
! decide_count(unsigned rand_byte)
  {
! 	return 96 + (rand_byte & 0x1F);
  }
  
  int
! pgp_s2k_fill(PGP_S2K *s2k, int mode, int digest_algo)
  {
  	int			res = 0;
  	uint8		tmp;
--- 200,223 ----
   * Too small: weak
   * Too big: slow
   * gpg defaults to 96 => 65536 iters
!  * For our default let it float a bit: 96 + 32 => 262144 iters
!  * Otherwise, find the smallest iteration which provides
!  * at least the specified count.
   */
  static int
! decide_count(unsigned rand_byte,int count)
  {
! 	int iter;
! 	if (count == -1)
! 		return 96 + (rand_byte & 0x1F);
! 	for (iter=0; iter<=255; iter++)
! 	if (iter_to_count(iter) >= count)
! 		return iter;
! 	return 255;
  }
  
  int
! pgp_s2k_fill(PGP_S2K *s2k, int mode, int digest_algo, int count)
  {
  	int			res = 0;
  	uint8		tmp;
*************** pgp_s2k_fill(PGP_S2K *s2k, int mode, int
*** 231,237 ****
  			res = px_get_pseudo_random_bytes(&tmp, 1);
  			if (res < 0)
  				break;
! 			s2k->iter = decide_count(tmp);
  			break;
  		default:
  			res = PXE_PGP_BAD_S2K_MODE;
--- 239,245 ----
  			res = px_get_pseudo_random_bytes(&tmp, 1);
  			if (res < 0)
  				break;
! 			s2k->iter = decide_count(tmp,count);
  			break;
  		default:
  			res = PXE_PGP_BAD_S2K_MODE;
diff --git a/contrib/pgcrypto/pgp.c b/contrib/pgcrypto/pgp.c
new file mode 100644
index 03fe48f..f0e016c
*** a/contrib/pgcrypto/pgp.c
--- b/contrib/pgcrypto/pgp.c
***************
*** 40,45 ****
--- 40,46 ----
  static int	def_cipher_algo = PGP_SYM_AES_128;
  static int	def_s2k_cipher_algo = -1;
  static int	def_s2k_mode = PGP_S2K_ISALTED;
+ static int	def_s2k_count = -1;
  static int	def_s2k_digest_algo = PGP_DIGEST_SHA1;
  static int	def_compress_algo = PGP_COMPR_NONE;
  static int	def_compress_level = 6;
*************** pgp_init(PGP_Context **ctx_p)
*** 206,211 ****
--- 207,213 ----
  	ctx->cipher_algo = def_cipher_algo;
  	ctx->s2k_cipher_algo = def_s2k_cipher_algo;
  	ctx->s2k_mode = def_s2k_mode;
+ 	ctx->s2k_count = def_s2k_count;
  	ctx->s2k_digest_algo = def_s2k_digest_algo;
  	ctx->compress_algo = def_compress_algo;
  	ctx->compress_level = def_compress_level;
*************** pgp_set_s2k_mode(PGP_Context *ctx, int m
*** 270,275 ****
--- 272,288 ----
  }
  
  int
+ pgp_set_s2k_count(PGP_Context *ctx, int count)
+ {
+ 	if (ctx->s2k_mode == PGP_S2K_ISALTED && count >= 1024 && count <= 65011712) 
+ 	{
+ 		ctx->s2k_count = count;
+ 		return PXE_OK;
+ 	}
+ 	return PXE_ARGUMENT_ERROR;
+ }
+ 
+ int
  pgp_set_compress_algo(PGP_Context *ctx, int algo)
  {
  	switch (algo)
diff --git a/contrib/pgcrypto/pgp.h b/contrib/pgcrypto/pgp.h
new file mode 100644
index 62b8517..c3e86e3
*** a/contrib/pgcrypto/pgp.h
--- b/contrib/pgcrypto/pgp.h
***************
*** 34,39 ****
--- 34,41 ----
  #include "mbuf.h"
  #include "px.h"
  
+ #define iter_to_count(cval) (((unsigned) 16 + (cval & 15)) << ((cval >> 4) + 6))
+ 
  enum PGP_S2K_TYPE
  {
  	PGP_S2K_SIMPLE = 0,
*************** struct PGP_Context
*** 138,143 ****
--- 140,146 ----
  	 */
  	PGP_S2K		s2k;
  	int			s2k_mode;
+ 	int			s2k_count;
  	int			s2k_digest_algo;
  	int			s2k_cipher_algo;
  	int			cipher_algo;
*************** const char *pgp_get_cipher_name(int code
*** 243,248 ****
--- 246,252 ----
  
  int			pgp_set_cipher_algo(PGP_Context *ctx, const char *name);
  int			pgp_set_s2k_mode(PGP_Context *ctx, int type);
+ int			pgp_set_s2k_count(PGP_Context *ctx, int count);
  int			pgp_set_s2k_cipher_algo(PGP_Context *ctx, const char *name);
  int			pgp_set_s2k_digest_algo(PGP_Context *ctx, const char *name);
  int			pgp_set_convert_crlf(PGP_Context *ctx, int doit);
*************** int			pgp_load_cipher(int c, PX_Cipher *
*** 267,273 ****
  int			pgp_get_cipher_key_size(int c);
  int			pgp_get_cipher_block_size(int c);
  
! int			pgp_s2k_fill(PGP_S2K *s2k, int mode, int digest_algo);
  int			pgp_s2k_read(PullFilter *src, PGP_S2K *s2k);
  int			pgp_s2k_process(PGP_S2K *s2k, int cipher, const uint8 *key, int klen);
  
--- 271,277 ----
  int			pgp_get_cipher_key_size(int c);
  int			pgp_get_cipher_block_size(int c);
  
! int			pgp_s2k_fill(PGP_S2K *s2k, int mode, int digest_algo, int count);
  int			pgp_s2k_read(PullFilter *src, PGP_S2K *s2k);
  int			pgp_s2k_process(PGP_S2K *s2k, int cipher, const uint8 *key, int klen);
  
diff --git a/contrib/pgcrypto/sql/pgp-encrypt.sql b/contrib/pgcrypto/sql/pgp-encrypt.sql
new file mode 100644
index a9ac0b9..1dd575d
*** a/contrib/pgcrypto/sql/pgp-encrypt.sql
--- b/contrib/pgcrypto/sql/pgp-encrypt.sql
*************** select pgp_sym_decrypt(
*** 55,60 ****
--- 55,68 ----
  	pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'),
  	'key', 'expect-s2k-mode=3');
  
+ -- s2k count change
+ select pgp_sym_decrypt(
+ 	pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
+ 	'key', 'expect-s2k-count=1024');
+ select pgp_sym_decrypt(
+ 	pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
+ 	'key', 'expect-s2k-count=65000000'); -- rounded up
+ 
  -- s2k digest change
  select pgp_sym_decrypt(
  	pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'),
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
new file mode 100644
index bfcbe02..21a884b
*** a/doc/src/sgml/pgcrypto.sgml
--- b/doc/src/sgml/pgcrypto.sgml
*************** Applies to: pgp_sym_encrypt
*** 859,864 ****
--- 859,877 ----
    </sect4>
  
    <sect4>
+    <title>s2k-count</title>
+ 
+    <para>
+     The number of iterations of the S2K algorithm to use.  It must
+     be a value between 1024 and 65011712, inclusive.
+    </para>
+ <literallayout>
+ Default: A random value bewteen 65536 and 253952
+ Applies to: pgp_sym_encrypt, only with s2k-mode=3
+ </literallayout>
+   </sect4>
+ 
+   <sect4>
     <title>s2k-digest-algo</title>
  
     <para>
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to