1
$\begingroup$

The following code generates a deterministic stream of pseudo-random bytes, using SHA3.

use sha3::{Digest, Sha3_256};

pub struct ShaStream {
    passhash: [u8; 32],
    nonce: [u8; 32],
    index: u64,
    block: [u8; 32],
    hasher: Sha3_256,
}
    
impl ShaStream {
    pub fn new(passhash: [u8; 32], nonce: [u8; 32]) -> ShaStream {
        ShaStream {passhash, nonce, index: 0, block: [0; 32], hasher: Sha3_256::new()}
    }
}

impl Iterator for ShaStream {
    type Item = u8;
    
    fn next(&mut self) -> Option<u8> {
        // make a new block, every 32 bytes
        if self.index % 32 == 0 {
            self.hasher.update(self.index.to_be_bytes());
            self.hasher.update(self.passhash);
            self.hasher.update(self.block);
            self.hasher.update(self.nonce);
            self.block = self.hasher.finalize_reset().into();
        }
        let value = self.block[self.index as usize % 32];
        self.index += 1;
        Some(value)
    }
}

(passhash is the hash of a compressed, whitened, long passphrase. nonces must not be reused.)

If I XOR this stream with a file, will I get strong encryption?

Or are there weaknesses which I haven't considered?

P.S. The results of dieharder, on 1 MB of output, look reasonable - but I am not sure how to judge them. Is a single WEAK a problem?

$ dieharder -a -f xor.bin 
#=============================================================================#
#            dieharder version 3.31.1 Copyright 2003 Robert G. Brown          #
#=============================================================================#
   rng_name    |           filename             |rands/second|
        mt19937|                         xor.bin|  1.02e+08  |
#=============================================================================#
        test_name   |ntup| tsamples |psamples|  p-value |Assessment
#=============================================================================#
   diehard_birthdays|   0|       100|     100|0.23634314|  PASSED  
      diehard_operm5|   0|   1000000|     100|0.44073675|  PASSED  
  diehard_rank_32x32|   0|     40000|     100|0.09071670|  PASSED  
    diehard_rank_6x8|   0|    100000|     100|0.98413526|  PASSED  
   diehard_bitstream|   0|   2097152|     100|0.80928251|  PASSED  
        diehard_opso|   0|   2097152|     100|0.30077190|  PASSED  
        diehard_oqso|   0|   2097152|     100|0.91046849|  PASSED  
         diehard_dna|   0|   2097152|     100|0.23134815|  PASSED  
diehard_count_1s_str|   0|    256000|     100|0.28623905|  PASSED  
diehard_count_1s_byt|   0|    256000|     100|0.53924476|  PASSED  
 diehard_parking_lot|   0|     12000|     100|0.68281092|  PASSED  
    diehard_2dsphere|   2|      8000|     100|0.78529773|  PASSED  
    diehard_3dsphere|   3|      4000|     100|0.31723984|  PASSED  
     diehard_squeeze|   0|    100000|     100|0.96834170|  PASSED  
        diehard_sums|   0|       100|     100|0.48488812|  PASSED  
        diehard_runs|   0|    100000|     100|0.83280719|  PASSED  
        diehard_runs|   0|    100000|     100|0.91027127|  PASSED  
       diehard_craps|   0|    200000|     100|0.37347197|  PASSED  
       diehard_craps|   0|    200000|     100|0.91605252|  PASSED  
 marsaglia_tsang_gcd|   0|  10000000|     100|0.67400210|  PASSED  
 marsaglia_tsang_gcd|   0|  10000000|     100|0.36182964|  PASSED  
         sts_monobit|   1|    100000|     100|0.04375935|  PASSED  
            sts_runs|   2|    100000|     100|0.99425607|  PASSED  
          sts_serial|   1|    100000|     100|0.82838574|  PASSED  
          sts_serial|   2|    100000|     100|0.86544431|  PASSED  
          sts_serial|   3|    100000|     100|0.82113526|  PASSED  
          sts_serial|   3|    100000|     100|0.99367519|  PASSED  
          sts_serial|   4|    100000|     100|0.43314255|  PASSED  
          sts_serial|   4|    100000|     100|0.81866202|  PASSED  
          sts_serial|   5|    100000|     100|0.50034343|  PASSED  
          sts_serial|   5|    100000|     100|0.64324459|  PASSED  
          sts_serial|   6|    100000|     100|0.88971853|  PASSED  
          sts_serial|   6|    100000|     100|0.97804225|  PASSED  
          sts_serial|   7|    100000|     100|0.82763353|  PASSED  
          sts_serial|   7|    100000|     100|0.73522587|  PASSED  
          sts_serial|   8|    100000|     100|0.99950104|   WEAK
          sts_serial|   8|    100000|     100|0.50056257|  PASSED  
          sts_serial|   9|    100000|     100|0.17834420|  PASSED  
          sts_serial|   9|    100000|     100|0.81670387|  PASSED  
          sts_serial|  10|    100000|     100|0.06053301|  PASSED  
          sts_serial|  10|    100000|     100|0.16833877|  PASSED  
          sts_serial|  11|    100000|     100|0.45143111|  PASSED  
          sts_serial|  11|    100000|     100|0.90007517|  PASSED  
          sts_serial|  12|    100000|     100|0.29181206|  PASSED  
          sts_serial|  12|    100000|     100|0.23372932|  PASSED  
          sts_serial|  13|    100000|     100|0.26269445|  PASSED  
          sts_serial|  13|    100000|     100|0.39691685|  PASSED  
          sts_serial|  14|    100000|     100|0.27804065|  PASSED  
          sts_serial|  14|    100000|     100|0.55210331|  PASSED  
          sts_serial|  15|    100000|     100|0.02302363|  PASSED  
          sts_serial|  15|    100000|     100|0.04573078|  PASSED  
          sts_serial|  16|    100000|     100|0.03990599|  PASSED  
          sts_serial|  16|    100000|     100|0.96326029|  PASSED  
         rgb_bitdist|   1|    100000|     100|0.84215365|  PASSED  
         rgb_bitdist|   2|    100000|     100|0.49349609|  PASSED  
         rgb_bitdist|   3|    100000|     100|0.18203352|  PASSED  
         rgb_bitdist|   4|    100000|     100|0.42475472|  PASSED  
         rgb_bitdist|   5|    100000|     100|0.24511091|  PASSED  
         rgb_bitdist|   6|    100000|     100|0.92089949|  PASSED  
         rgb_bitdist|   7|    100000|     100|0.84628916|  PASSED  
         rgb_bitdist|   8|    100000|     100|0.22711540|  PASSED  
         rgb_bitdist|   9|    100000|     100|0.34303650|  PASSED  
         rgb_bitdist|  10|    100000|     100|0.59588524|  PASSED  
         rgb_bitdist|  11|    100000|     100|0.91612653|  PASSED  
         rgb_bitdist|  12|    100000|     100|0.27207545|  PASSED  
rgb_minimum_distance|   2|     10000|    1000|0.75765186|  PASSED  
rgb_minimum_distance|   3|     10000|    1000|0.34765391|  PASSED  
rgb_minimum_distance|   4|     10000|    1000|0.56023937|  PASSED  
rgb_minimum_distance|   5|     10000|    1000|0.79620902|  PASSED  
    rgb_permutations|   2|    100000|     100|0.99300316|  PASSED  
    rgb_permutations|   3|    100000|     100|0.28066491|  PASSED  
    rgb_permutations|   4|    100000|     100|0.36539121|  PASSED  
    rgb_permutations|   5|    100000|     100|0.33314486|  PASSED  
      rgb_lagged_sum|   0|   1000000|     100|0.15207425|  PASSED  
      rgb_lagged_sum|   1|   1000000|     100|0.07013932|  PASSED  
      rgb_lagged_sum|   2|   1000000|     100|0.27477884|  PASSED  
      rgb_lagged_sum|   3|   1000000|     100|0.95111023|  PASSED  
      rgb_lagged_sum|   4|   1000000|     100|0.02796184|  PASSED  
      rgb_lagged_sum|   5|   1000000|     100|0.45874461|  PASSED  
      rgb_lagged_sum|   6|   1000000|     100|0.18328909|  PASSED  
      rgb_lagged_sum|   7|   1000000|     100|0.02821152|  PASSED  
      rgb_lagged_sum|   8|   1000000|     100|0.16271011|  PASSED  
      rgb_lagged_sum|   9|   1000000|     100|0.63672591|  PASSED  
      rgb_lagged_sum|  10|   1000000|     100|0.97828167|  PASSED  
      rgb_lagged_sum|  11|   1000000|     100|0.71885077|  PASSED  
      rgb_lagged_sum|  12|   1000000|     100|0.75394600|  PASSED  
      rgb_lagged_sum|  13|   1000000|     100|0.61132545|  PASSED  
      rgb_lagged_sum|  14|   1000000|     100|0.36991248|  PASSED  
      rgb_lagged_sum|  15|   1000000|     100|0.98795179|  PASSED  
      rgb_lagged_sum|  16|   1000000|     100|0.39086917|  PASSED  
      rgb_lagged_sum|  17|   1000000|     100|0.72342485|  PASSED  
      rgb_lagged_sum|  18|   1000000|     100|0.32210912|  PASSED  
      rgb_lagged_sum|  19|   1000000|     100|0.23889372|  PASSED  
      rgb_lagged_sum|  20|   1000000|     100|0.02979572|  PASSED  
      rgb_lagged_sum|  21|   1000000|     100|0.99132612|  PASSED  
      rgb_lagged_sum|  22|   1000000|     100|0.63051091|  PASSED  
      rgb_lagged_sum|  23|   1000000|     100|0.40911221|  PASSED  
      rgb_lagged_sum|  24|   1000000|     100|0.95798515|  PASSED  
      rgb_lagged_sum|  25|   1000000|     100|0.81390689|  PASSED  
      rgb_lagged_sum|  26|   1000000|     100|0.21471266|  PASSED  
      rgb_lagged_sum|  27|   1000000|     100|0.97590860|  PASSED  
      rgb_lagged_sum|  28|   1000000|     100|0.65097372|  PASSED  
      rgb_lagged_sum|  29|   1000000|     100|0.40351426|  PASSED  
      rgb_lagged_sum|  30|   1000000|     100|0.67212314|  PASSED  
      rgb_lagged_sum|  31|   1000000|     100|0.36602753|  PASSED  
      rgb_lagged_sum|  32|   1000000|     100|0.54699284|  PASSED  
     rgb_kstest_test|   0|     10000|    1000|0.47824082|  PASSED  
     dab_bytedistrib|   0|  51200000|       1|0.27617055|  PASSED  
             dab_dct| 256|     50000|       1|0.06378066|  PASSED  
Preparing to run test 207.  ntuple = 0
        dab_filltree|  32|  15000000|       1|0.51156512|  PASSED  
        dab_filltree|  32|  15000000|       1|0.77505955|  PASSED  
Preparing to run test 208.  ntuple = 0
       dab_filltree2|   0|   5000000|       1|0.92507779|  PASSED  
       dab_filltree2|   1|   5000000|       1|0.86783259|  PASSED  
Preparing to run test 209.  ntuple = 0
        dab_monobit2|  12|  65000000|       1|0.39811014|  PASSED  
$\endgroup$
  • 2
    $\begingroup$ Hash functions have different security properties than stream ciphers. But statistical tests like dieharder are almost useless with regards to security: It can not prove security, it can only filter out those, which are easily broken by clever statistics. Example: linear feedback shift registers have really good statistical properties. But from a security point of view they are very insecure. $\endgroup$ – tylo Sep 19 at 15:19
  • 1
    $\begingroup$ SHA3/Keccak already offer far superior modes than the stream cipher model you are suggesting, if you want a stream cipher, AES in CTR mode is a orders of magnitude faster $\endgroup$ – Richie Frame Sep 20 at 2:18
  • 1
    $\begingroup$ There's recent question about using Keccak (the SHA3 permutation) as sole primitive for the entire TLS1.3 ciphersuite and possibility of (ab)using SHAKE as stream cipher $\endgroup$ – DannyNiu Sep 22 at 1:35
1
+100
$\begingroup$

I think the consensus is that there's nothing obviously insecure with this, and that I can safely carry on hammering-in nails with my spanner.

As long as the hash used also functions as a CSPRNG you're fine. Any (secure) hashing algorithm would create a secure stream cipher in this model.

It should be noted that not all hashing algorithms must function as a CSPRNG. This is not a requirement for a secure hash, and secure hashes have been constructed without this property. However, the hashing algorithms I reference in stating this are rarely used and just as rarely known about. SHA-3 acts as a CSPRNG so this note is irrelevant in your case.

The ciphertext does become distinguishable from random after the hash's state repeats. For example, a 512 bit hash would become distinguishable from random after $512 * 2^{512}$ bits. A 384-bit hash after $384 * 2^{384}$ bits... You get the idea.

| improve this answer | |
$\endgroup$
1
$\begingroup$

I wrote something similar in C using SHA256 several years ago and it also passed all the DieHarder tests.

I intended to use it as a stream cipher in a project that employed variable length data, i.e. conversational messages, where both sides remained synchronised and therefor could continue calling off blocks of pseudo-random numbers for use with XOR.

Other than the potential issue with synchronisation I do not understand why the field of cryptography is obsessed with block ciphers when many real-world applications need stream ciphers.

| improve this answer | |
$\endgroup$
  • 2
    $\begingroup$ You can use a block cipher in counter mode (CTR, or even better GCM), and that works basically like a stream cipher. If you look at the current recommendations of the BSI here. They explicitly stated, they can't recommend any dedicated stream cipher. $\endgroup$ – tylo Sep 19 at 15:11
  • $\begingroup$ "Other than the potential issue with synchronisation I do not understand why the field of cryptography is obsessed with block ciphers when many real-world applications need stream ciphers." Hardly obsessed. Block ciphers are used because they're easy to actually analyze for security. You'll also see dedicated permutations (like used in Salsa, ChaCha, Gimli, and others). But block ciphers and permutations are almost always used to build stream ciphers (with CTR mode + a MAC or in a dedicated 1-pass AEAD mode). $\endgroup$ – SAI Peregrinus Sep 19 at 19:03
0
$\begingroup$

I think the consensus is that there's nothing obviously insecure with this, and that I can safely carry on hammering-in nails with my spanner.

Perhaps this is about trust? If I trusted SHA3, but not block ciphers, would this be reasonable?

(I have no reason to trust SHA3 and not block ciphers.)

| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.