Why is it that you hear people saying, “don’t roll your own crypto”? It can’t be that bad, right? I mean, if the code gives the correct outputs when given the correct inputs?
Everything in cryptography depends upon “high quality” random numbers, and lots of them. People get into semi-informed flamewars about what “entropy” means, government agencies sneak backdoors into algorithms, performance matters, secrecy matters, and unpredictability matters. The standard which defines four randomness generators is NIST Special Publication 800–90. One of the four raised suspicions because it (Dual_EC_DRBG) was three times slower than any of the others.
Joe… well, Joe sends us his own code, which “fixed” this. He made one of the others slower. Using MySQL stored procedures. Just bear in mind, with the below, that it’s still cleaner, more comprehensible, and generally saner than OpenSSL.
--
-- prng_nistctr_sp
-- Implements the NIST SP 800-90 CTR_DRBG cryptographic random number generator standard
-- with MySQL AES_ENCRYPT() as the block cipher (256 bit seed, 128 bit output)
--
DELIMITER //
DROP FUNCTION prng.make_128bit_block//
CREATE DEFINER = 'prng'@'localhost'
FUNCTION prng.make_128bit_block (p_h BIGINT UNSIGNED, p_l BIGINT UNSIGNED)
RETURNS TEXT
LANGUAGE SQL
CONTAINS SQL
DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
RETURN UNHEX( CONCAT(LPAD(HEX(p_h),16,'0'),LPAD(HEX(p_l),16,'0')) );
END;
//
DROP PROCEDURE prng.inc_128bit_value//
CREATE DEFINER = 'prng'@'localhost'
PROCEDURE prng.inc_128bit_value (INOUT p_h BIGINT UNSIGNED, INOUT p_l BIGINT UNSIGNED)
LANGUAGE SQL
CONTAINS SQL
DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
IF p_l < 18446744073709551615 THEN
SET p_l = p_l + 1;
ELSE
SET p_l = 0;
IF p_h < 18446744073709551615 THEN
SET p_h = p_h + 1;
ELSE
SET p_h = 0;
END IF;
END IF;
END;
//
DROP PROCEDURE prng.nistctr_block_encrypt//
CREATE DEFINER = 'prng'@'localhost'
PROCEDURE prng.nistctr_block_encrypt (p_vh BIGINT UNSIGNED, p_vl BIGINT UNSIGNED, p_kh BIGINT UNSIGNED, p_kl BIGINT UNSIGNED, OUT o_h BIGINT UNSIGNED, OUT o_l BIGINT UNSIGNED)
LANGUAGE SQL
CONTAINS SQL
DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
DECLARE hexblock TEXT;
SET hexblock = HEX( AES_ENCRYPT(prng.make_128bit_block(p_vh, p_vl), prng.make_128bit_block(p_kh, p_kl)) );
SET o_h = CONV( SUBSTR(hexblock, 1, 16), 16, 10);
SET o_l = CONV( SUBSTR(hexblock, 17, 16), 16, 10);
END;
//
DROP PROCEDURE prng.nistctr_update//
CREATE DEFINER = 'prng'@'localhost'
PROCEDURE prng.nistctr_update (INOUT p_vh BIGINT UNSIGNED, INOUT p_vl BIGINT UNSIGNED, INOUT p_kh BIGINT UNSIGNED, INOUT p_kl BIGINT UNSIGNED, IN p_dah BIGINT UNSIGNED, IN p_dal BIGINT UNSIGNED, IN p_dbh BIGINT UNSIGNED, IN p_dbl BIGINT UNSIGNED)
LANGUAGE SQL
CONTAINS SQL
NOT DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
DECLARE tah,tal,tbh,tbl BIGINT UNSIGNED DEFAULT 0;
CALL prng.inc_128bit_value(p_vh, p_vl);
CALL prng.nistctr_block_encrypt(p_vh, p_vl, p_kh, p_kl, tah, tal);
CALL prng.inc_128bit_value(p_vh, p_vl);
CALL prng.nistctr_block_encrypt(p_vh, p_vl, p_kh, p_kl, tbh, tbl);
SET tah = tah ^ p_dah; SET tal = tal ^ p_dal;
SET tbh = tbh ^ p_dbh; SET tbl = tbl ^ p_dbl;
SET p_kh = tah; SET p_kl = tal;
SET p_vh = tbh; SET p_vl = tbl;
END;
//
DROP PROCEDURE prng.nistctr_instantiate//
CREATE DEFINER = 'prng'@'localhost'
PROCEDURE prng.nistctr_instantiate (INOUT p_vh BIGINT UNSIGNED, INOUT p_vl BIGINT UNSIGNED, INOUT p_kh BIGINT UNSIGNED, INOUT p_kl BIGINT UNSIGNED, IN p_dah BIGINT UNSIGNED, IN p_dal BIGINT UNSIGNED, IN p_dbh BIGINT UNSIGNED, IN p_dbl BIGINT UNSIGNED)
LANGUAGE SQL
MODIFIES SQL DATA
NOT DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
SET p_kh = 0; SET p_kl = 0;
SET p_vh = 0; SET p_vl = 0;
CALL prng.nistctr_update(p_vh, p_vl, p_kh, p_kl, p_dah, p_dal, p_dbh, p_dbl);
END;
//
DROP PROCEDURE prng.nistctr_reseed//
CREATE DEFINER = 'prng'@'localhost'
PROCEDURE prng.nistctr_reseed (INOUT p_vh BIGINT UNSIGNED, INOUT p_vl BIGINT UNSIGNED, INOUT p_kh BIGINT UNSIGNED, INOUT p_kl BIGINT UNSIGNED, IN p_dah BIGINT UNSIGNED, IN p_dal BIGINT UNSIGNED, IN p_dbh BIGINT UNSIGNED, IN p_dbl BIGINT UNSIGNED)
LANGUAGE SQL
MODIFIES SQL DATA
NOT DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
CALL prng.nistctr_update(p_vh, p_vl, p_kh, p_kl, p_dah, p_dal, p_dbh, p_dbl);
END;
//
DROP PROCEDURE prng.nistctr_generate//
CREATE DEFINER = 'prng'@'localhost'
PROCEDURE prng.nistctr_generate (INOUT p_vh BIGINT UNSIGNED, INOUT p_vl BIGINT UNSIGNED, INOUT p_kh BIGINT UNSIGNED, INOUT p_kl BIGINT UNSIGNED, OUT o_h BIGINT UNSIGNED, OUT o_l BIGINT UNSIGNED, IN p_dah BIGINT UNSIGNED, IN p_dal BIGINT UNSIGNED, IN p_dbh BIGINT UNSIGNED, IN p_dbl BIGINT UNSIGNED)
LANGUAGE SQL
MODIFIES SQL DATA
NOT DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
SET o_h = NULL; SET o_l = NULL;
IF p_dal IS NULL THEN SET p_dal = 0; END IF;
IF p_dbh IS NULL THEN SET p_dbh = 0; END IF;
IF p_dbl IS NULL THEN SET p_dbl = 0; END IF;
IF p_dah IS NULL THEN
SET p_dah = 0;
ELSE
CALL prng.nistctr_update(p_vh, p_vl, p_kh, p_kl, p_dah, p_dal, p_dbh, p_dbl);
END IF;
CALL prng.inc_128bit_value(p_vh, p_vl);
CALL prng.nistctr_block_encrypt(p_vh, p_vl, p_kh, p_kl, o_h, o_l);
CALL prng.nistctr_update(p_vh, p_vl, p_kh, p_kl, p_dah, p_dal, p_dbh, p_dbl);
END;
//
DROP FUNCTION arosystem.hex_prng_generate_block//
CREATE DEFINER = 'prng'@'localhost'
FUNCTION arosystem.hex_prng_generate_block ()
RETURNS TEXT
MODIFIES SQL DATA
NOT DETERMINISTIC
SQL SECURITY DEFINER
BEGIN
DECLARE nvh,nvl,nkh,nkl BIGINT UNSIGNED DEFAULT 0;
DECLARE oh,ol BIGINT UNSIGNED DEFAULT NULL;
SELECT vh,vl,kh,kl INTO nvh,nvl,nkh,nkl FROM prng.prng_nistctr_state WHERE usable = TRUE LIMIT 1;
CALL prng.nistctr_generate(nvh,nvl,nkh,nkl,oh,ol,NULL,NULL,NULL,NULL);
UPDATE prng.prng_nistctr_state SET vh=nvh,vl=nvl,kh=nkh,kl=nkl, reseed_counter = reseed_counter + 1;
RETURN HEX( prng.make_128bit_block(oh,ol) );
END;
//
DROP PROCEDURE prng.prng_init_hex//
CREATE DEFINER = 'prng'@'localhost'
PROCEDURE prng.prng_init_hex (hexseed TEXT)
MODIFIES SQL DATA
NOT DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
DECLARE nvh,nvl,nkh,nkl BIGINT UNSIGNED DEFAULT 0;
DECLARE dah,dal,dbh,dbl BIGINT UNSIGNED DEFAULT 0;
SET hexseed = LPAD(hexseed, 64, '0');
SET dah = CONV( SUBSTR(hexseed, 1, 16), 16, 10);
SET dal = CONV( SUBSTR(hexseed, 17, 16), 16, 10);
SET dbh = CONV( SUBSTR(hexseed, 33, 16), 16, 10);
SET dbl = CONV( SUBSTR(hexseed, 49, 16), 16, 10);
CALL prng.nistctr_instantiate(nvh,nvl,nkh,nkl,dah,dal,dbh,dbl);
UPDATE prng.prng_nistctr_state SET vh=nvh,vl=nvl,kh=nkh,kl=nkl, usable = TRUE, reseed_counter = 1;
END;
//
Joe at least imbued a certain something into the dance of bouncing in and out of hex representations; safely passing around the strings, and splitting the 128-bit values into 64-bit chunks, then recombining later. Even the modulo arithmetic, implemented by comparison to a hard-coded representation of 2^64–1, before either adding 1 or setting it to 0.
Yet, in the razzle-dazzle of the dance, certain teensy things may have slipped from Joe’s attention. Is that seed the required minimum size? What happens in the presence of concurrency? Oops- looks like two concurrent callerts to hex_prng_generate_block
can get the same entropy, making the same updates to the state. Better hope this isn’t being used for keying material to protect against eavesdropping, or an attacker can get the same keys by simply talking at the same time. And how is this initialized for the very first time?
Just because it looks like it works, returning good values, in good conditions, isn’t enough for crypto. Enjoy Joe’s “masterpiece”. It might wake you up better than that cup of coffee as you follow along, but please, don’t try this at home.