Erlang :crypto.stream_init returns empty state

Hi all,

I’m trying to use the erlang :crypto.stream_init function. I’m running an Ubuntu VM on a Windows laptop. When I run on Windows, it works fine. When I run this on the Ubuntu VM, I get an emty string instead of the opaque state.

iex(1)> iv = :crypto.strong_rand_bytes(16)
<<142, 93, 70, 234, 188, 142, 90, 77, 140, 93, 219, 36, 69, 159, 95, 171>>
iex(2)> key = :crypto.strong_rand_bytes(32)
<<160, 142, 195, 53, 122, 138, 78, 212, 131, 136, 1, 56, 195, 181, 236, 144,
  161, 220, 39, 183, 165, 176, 216, 247, 100, 229, 246, 145, 250, 153, 119, 80>>
iex(3)> :crypto.stream_init(:aes_ctr, key, iv)
{:aes_ctr, ""}

The typical return value from stream_init would be something like this:

{:aes_ctr,
 {<<14, 171, 194, 22, 114, 150, 69, 81, 205, 240, 12, 32, 61, 187, 248, 121, 71,
    14, 159, 93, 231, 128, 63, 242, 3, 129, 67, 147, 170, 186, 155, 115>>,
  <<10, 218, 217, 112, 68, 203, 50, 40, 60, 109, 205, 248, 157, 110, 66, 189>>,
  <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>, 0}}

Does anyone know why this might happen?

Is your /dev/random device working? Sounds like it might not have enough entropy built up as an initial guess?

Are you sure that it doesn’t work if you pass the empty binary as usual? The return type is opaque so you can’t really make assumptions. What appears to be an empty binary might be NIF reference that looks like an empty binary.

Yes, it seems to be working. That was my initial thought too. The strange thing is that the operation that actually needs entropy, strong_rand_bytes completes without a problem. The docs even indicate that this will throw exception low_entropy if there isn’t sufficient randomness.

On the other hand, my understanding is that stream_init should be completely deterministic with no entropy needed.

You know, it may actually be working. It does seem to let me encrypt stuff with that state. I was just so freaked out by the difference in the way the state looked on Windows/Linux that I wasn’t comfortable using it for a high security application without understanding.

Is this expected that windows would produce a data structure and Linux a NIF reference (appearing as an empty binary)?

NIF’s can generate whatever they want, and Windows and Linux do randomness very differently, so it would make sense. ^.^

It would not make sense. When the same (stream) cipher is initialized with the same key and iv, the state of the cipher should be exactly the same, regardless of Operating System, programming language, or any other external influences. This is the case by definition of these kind of cipher algorithms. It is also the reason why a random iv+key combination is necessary for each new piece of information you want to encrypt (re-using them will make the cipher predictable).

I just copied over the iv and key binaries that your IEx gave back, and on Linux Mint I got the following result:

iex(45)> iv = <<142, 93, 70, 234, 188, 142, 90, 77, 140, 93, 219, 36, 69, 159, 95, 171>>   
<<142, 93, 70, 234, 188, 142, 90, 77, 140, 93, 219, 36, 69, 159, 95, 171>>
iex(46)> key = <<160, 142, 195, 53, 122, 138, 78, 212, 131, 136, 1, 56, 195, 181, 236, 144,
...(46)>   161, 220, 39, 183, 165, 176, 216, 247, 100, 229, 246, 145, 250, 153, 119, 80>>  
<<160, 142, 195, 53, 122, 138, 78, 212, 131, 136, 1, 56, 195, 181, 236, 144,
  161, 220, 39, 183, 165, 176, 216, 247, 100, 229, 246, 145, 250, 153, 119, 80>>
iex(47)> init = :crypto.stream_init(:aes_ctr, key, iv)                                     
{:aes_ctr,
 {<<160, 142, 195, 53, 122, 138, 78, 212, 131, 136, 1, 56, 195, 181, 236, 144,
    161, 220, 39, 183, 165, 176, 216, 247, 100, 229, 246, 145, 250, 153, 119,
    80>>,
  <<142, 93, 70, 234, 188, 142, 90, 77, 140, 93, 219, 36, 69, 159, 95, 171>>,
  <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>, 0}}
iex(48)> :crypto.stream_encrypt(init, "the quick brown fox jumps over the lazy dog")       
{{:aes_ctr,
  {<<160, 142, 195, 53, 122, 138, 78, 212, 131, 136, 1, 56, 195, 181, 236, 144,
     161, 220, 39, 183, 165, 176, 216, 247, 100, 229, 246, 145, 250, 153, 119,
     80>>,
   <<142, 93, 70, 234, 188, 142, 90, 77, 140, 93, 219, 36, 69, 159, 95, 174>>,
   <<145, 85, 24, 187, 212, 168, 150, 78, 166, 93, 3, 88, 175, 160, 207, 116>>,
   11}},
 <<59, 68, 179, 50, 61, 72, 75, 248, 36, 120, 9, 239, 37, 9, 135, 230, 25, 86,
   118, 143, 40, 228, 116, 148, 142, 198, 201, 222, 2, 173, 44, 205, 249, 48,
   56, 215, 181, 210, 239, 110, 194, 50, 100>>}

I would expect this result to be 1:1 the same on different computer systems. I am very confused about the empty binary you got as a result the first time.

@Qqwy I suppose this is actually the proof that this is working correctly. Although my state is displaying like an empty string, I get exactly the same cipher text as you do. If the state were really null or empty, that couldn’t happen.

iex(1)> iv = <<142, 93, 70, 234, 188, 142, 90, 77, 140, 93, 219, 36, 69, 159, 95, 171>>
<<142, 93, 70, 234, 188, 142, 90, 77, 140, 93, 219, 36, 69, 159, 95, 171>>
iex(2)> key = <<160, 142, 195, 53, 122, 138, 78, 212, 131, 136, 1, 56, 195, 181, 236, 144,
...(2)> 161, 220, 39, 183, 165, 176, 216, 247, 100, 229, 246, 145, 250, 153, 119, 80>>
<<160, 142, 195, 53, 122, 138, 78, 212, 131, 136, 1, 56, 195, 181, 236, 144,
  161, 220, 39, 183, 165, 176, 216, 247, 100, 229, 246, 145, 250, 153, 119, 80>>
iex(3)> init = :crypto.stream_init(:aes_ctr, key, iv)
{:aes_ctr, ""}
iex(4)> :crypto.stream_encrypt(init, "the quick brown fox jumps over the lazy dog")
{{:aes_ctr, ""},
 <<59, 68, 179, 50, 61, 72, 75, 248, 36, 120, 9, 239, 37, 9, 135, 230, 25, 86,
   118, 143, 40, 228, 116, 148, 142, 198, 201, 222, 2, 173, 44, 205, 249, 48,
   56, 215, 181, 210, 239, 110, 194, 50, 100>>}

So, I think I’m good. Thanks all!

1 Like

Try using

iex> i iv

What gets printed as the result in iex is the inspect(foo) and that can sometimes mislead you as to what is actually in foo.

1 Like