DCIT / perl-CryptX

https://metacpan.org/pod/CryptX
Other
35 stars 23 forks source link

CTR mode appears to preincrement IV once before emitting a single byte of encrypted stream #38

Closed alankila closed 7 years ago

alankila commented 7 years ago

First of all, I'm very pleased at having discovered CryptX. This library is very pleasant to use for cryptograhpic operations and has trivially simple API for wide range of tasks that I've undertaken, including RSA key generation, PEM encryption, RSA + AES encryption, HMAC, and finally XTS and CTR cipher modes. To put it shortly, its API and functionality are far superior to anything that has come before.

I'm currently developing a software system composed of a Java client and Perl server, and ran into an interoperability issue when designing a stream cipher that is used to encrypt files that are temporarily stored on server's disk using AES-128-CTR cipher with a random session key that is encrypted with RSA. The cipher's IV is initialized to the file seek offset divided by 16. In other words, the design is that the IV would start from 0 at the beginning of file, and counts up every time a block of 16 bytes has been processed.

I noticed that to make Crypt::Mode::CTR and Java's AES/CTR/NoPadding modes compatible, Perl must use IV value of "\xff" x 16 and the big-endian IV counter mode (1), whereas Java uses IV "\x00" (and offers no choice regarding the way counter is incremented). Based on result of encrypting "\x00" x 16 using AES128 with key "\x00" x 16, Java appears to actually start its CTR stream from IV=0 as one would expect to happen, but Crypt::Mode::CTR has incremented the IV once before generating the stream.

This is not necessarily a bug -- I'm not sure how CTR mode is supposed to officially work -- but regardless such an implementation difference was a surprise to me, and required some debug time before I was able to make the client and server interoperate again. I would suggest changing this behavior such that the first block of the stream is generated from the IV provided by user. The mode parameter should probably be extended by another bit to retain backwards compatibility.

karel-m commented 7 years ago

Try to use $ctr_mode = 1 or $ctr_mode = 3

 my $m = Crypt::Mode::CTR->new($cipher_name, $ctr_mode);

 # $ctr_mode .... 0 little-endian counter (DEFAULT)
 #                1 big-endian counter
 #                2 little-endian + RFC3686 incrementing
 #                3 big-endian + RFC3686 incrementing

The difference is exactly in pre-incrementing: https://metacpan.org/source/MIK/CryptX-0.051/src/ltc/modes/ctr/ctr_start.c#L71

alankila commented 7 years ago

I am already using the ctr_mode=1, it seems to be the same as what Java is doing. But why is the preincrementing there in the first place?

alankila commented 7 years ago

Ah, I read that RFC mentioned here. The implementation found is here:

      CTRBLK := NONCE || IV || ONE
      FOR i := 1 to n-1 DO
        CT[i] := PT[i] XOR AES(CTRBLK)
        CTRBLK := CTRBLK + 1
      END
      CT[n] := PT[n] XOR TRUNC(AES(CTRBLK))

With a very specific structure for the counter, composed of 32-bit nonce, 64-bit IV, and 32-bit counter that starts from value 1. The first 96 bits are supposed to be supplied by the user, the last 32-bit are supposed to be set to big-endian number 1 for the first block in the stream. I suppose that preincrementing is a way to approximate this behavior when starting from some user-provided IV.

But my issue seems to be happening with ctr_mode=1, so the RFC should not be relevant.

karel-m commented 7 years ago

ctr_mode=1 shouldn't pre-increment

karel-m commented 7 years ago

However there might be a bug related to handling ctr_width argument at https://metacpan.org/source/MIK/CryptX-0.051/inc/CryptX_Mode_CTR.xs.inc#L19

karel-m commented 7 years ago

ctr_width is IMO correct.

If you have a short test case which does not produce expected result post it here.

alankila commented 7 years ago

Hmm. I'm sorry. I'm unable to replicate the issue now that I'm trying this again. I am starting to believe I've wasted your time. The following program works correctly despite I am sure I had trouble with something very similar to this last time:

    use Crypt::Mode::CTR; $ctr = Crypt::Mode::CTR->new("AES", 1); $ctr->start_encrypt("\x00" x 16, "\x00" x 16); print unpack "H*", $ctr->add("\x00" x 16)'
    66e94bd4ef8a2c3b884cfa59ca342b2e
karel-m commented 7 years ago

no problem