Java 加密字节后从Cipher获取更新的IV

Java 加密字节后从Cipher获取更新的IV,java,cryptography,Java,Cryptography,我正在处理一个需要附加到AES/CTR加密文件的项目。现在,由于是计数器模式,我知道我可以将计数器前进到任何位置,并在文件中的位置开始读取。但我想知道的是,是否有一种方法可以让我在使用密码后获取密码可以访问的当前IV Cipher c = Cipher.getInstance("AES/CTR/NoPadding"); SecretKeySpec keySpec = new SecretKeySpec(aeskey, "AES"); IvParameterSpec ivSpec = new Iv

我正在处理一个需要附加到AES/CTR加密文件的项目。现在,由于是计数器模式,我知道我可以将计数器前进到任何位置,并在文件中的位置开始读取。但我想知道的是,是否有一种方法可以让我在使用密码后获取密码可以访问的当前IV

Cipher c = Cipher.getInstance("AES/CTR/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aeskey, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);

c.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

CipherOutputStream cipher_out = new CipherOutputStream(output, c);

try {
    while (true) {
        cipher_out.write(input.readByte());
    }
} catch (EOFException e) {
}

byte curIV[] = c.getIV();
相反,我发现库里夫(curIV)并没有更新IV,而是与我最初输入ivSpec的IV相同。没有办法得到目前的静脉注射吗

其目的是存储:

<AES key><begin IV><current IV>
):


作为输入到CTR中,并且为了更新计数器,它被认为是一个大端数

这是作为上文中的
IvParameterSpec
提供的


除此之外,我想找回的是计数器,不管我们是想称之为IV,还是我们想称之为计数器,还是nonce+IV+计数器


有关Suns实现CTR的更多信息:

在试验“SunJCE”提供程序时,CTR模式下的AES如下,其中初始计数器值仅随每个连续块增加一。这与中给出的一般指导一致,当递增位数
m
是块
b
中的位数时

这与RFC3686相反。也就是说,整个计数器递增,而不仅仅是RFC3686中指定的有限部分

您可以通过计算块(从零开始)或测量密文的长度并按块大小进行整数除法来了解块索引。如果这些选项看起来太简单,您还可以将最后一个密码文本块与相应的纯文本进行异或运算,解密结果,然后减去IV以生成块索引

要追加,只需将IV设置为原始IV加上块索引。如果您正在编写可以以部分块结尾的流,那么您将需要做一些额外的工作来使流进入正确的状态

int BLOCK_SIZE = 16;
BigInteger MODULUS = BigInteger.ONE.shiftLeft(BLOCK_SIZE * 8);
...
/* Retrieve original IV. */
byte[] iv = ... ;

/* Compute the index of the block to which data will be appended. */
BigInteger block = BigInteger.valueOf(file.length() / BLOCK_SIZE);
/* Add the block to the nonce to find the current counter. */
BigInteger nonce = new BigInteger(1, iv);
byte[] tmp = nonce.add(block).mod(MODULUS).toByteArray();
/* Right-justify the counter value in a block-sized array. */
byte[] ctr = new byte[BLOCK_SIZE];
System.arraycopy(tmp, 0, ctr, BLOCK_SIZE - tmp.length, tmp.length);
/* Use this to initialize the appending cipher. */
IvParameterSpec param = new IvParameterSpec(ctr);

您的回答是正确的,
getIV
返回原始IV,而不是加密/解密后的当前IV

在Java中,在CTR模式下传递给AES分组密码的16个字节是IV加上当前的块号(就像两个字节都是big-endian格式的16字节bignums一样添加,请参见下面的代码)

请务必阅读,它有很多很好的建议来避免CTR模式的安全隐患(总结:永远不要用同一个IV加密两次)

对于您的用例,您只需要存储开始的IV加上块号(如果您可以通过其他方式获得文件大小,则甚至不需要存储块号)。您可以从中计算当前IV,以便进一步加密或随机搜索解密

给定块号(第一个块为0),计算要使用的正确IV的代码:

int block=。。。;
字节[]iv=。。。;
字节[]块字节=新字节[16];
对于(inti=0;i<4;i++)块字节[15-i]=(字节)(块>>8*i);
整数进位=0;
对于(int i=15;i>=0;i--){
整数和=(iv[i]&255)+(blockbytes[i]&255)+进位;
iv[i]=(字节)和;
进位=和>>8;
}
注意:我是通过了解代码的功能得到这个结果的——我在规范中没有看到它,所以算法可能会因提供者而异

下面是一个更完整的测试程序,您可以尝试:

import java.math.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class TestCTR {
  static SecretKeySpec keySpec = new SecretKeySpec(new BigInteger("112233445566778899aabbccddeeff00", 16).toByteArray(), "AES");
  static IvParameterSpec ivSpec = new IvParameterSpec(new BigInteger("66778899aaffffffffffffffffffffff", 16).toByteArray());

  public static void main(String[] args) throws Exception {
    byte[] plaintext = new byte[256];
    for (int i = 0; i < 256; i++) plaintext[i] = (byte)i;

    // encrypt with CTR mode                                                                                                           
    byte[] ciphertext = new byte[256];
    Cipher c = Cipher.getInstance("AES/CTR/NoPadding");
    c.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
    c.doFinal(plaintext, 0, 256, ciphertext, 0);

    // decrypt, implementing CTR mode ourselves                                                                                        
    Cipher b = Cipher.getInstance("AES/ECB/NoPadding");
    b.init(Cipher.ENCRYPT_MODE, keySpec);
    for (int block = 0; block < 16; block++) {
      byte[] iv = ivSpec.getIV();
      int carry = 0;
      byte[] blockbytes = new byte[16];
      for (int i = 0; i < 4; i++) blockbytes[15 - i] = (byte)(block >> 8*i);
      for (int i = 15; i >= 0; i--) {
        int sum = (iv[i] & 255) + (blockbytes[i] & 255) + carry;
        iv[i] = (byte)sum;
        carry = sum >> 8;
      }
      b.doFinal(iv, 0, 16, iv, 0);
      for (int i = 0; i < 16; i++) plaintext[block*16+i] = (byte)(ciphertext[block*16+i] ^ iv[i]);
    }

    // check it                                                                                                                        
    for(int i = 0; i < 256; i++) assert plaintext[i] == (byte)i;
  }
}
import java.math.*;
导入javax.crypto.*;
导入javax.crypto.spec.*;
公共类测试中心{
静态SecretKeySpec keySpec=新的SecretKeySpec(新的大整数(“11223344566778899aabbccddeeff00”,16).toByteArray(),“AES”);
静态IvParameterSpec ivSpec=新的IvParameterSpec(新的BigInteger(“66778899aaffffffffffffffffff”,16).toByteArray());
公共静态void main(字符串[]args)引发异常{
字节[]明文=新字节[256];
对于(inti=0;i<256;i++)纯文本[i]=(字节)i;
//使用CTR模式加密
字节[]密文=新字节[256];
Cipher c=Cipher.getInstance(“AES/CTR/NoPadding”);
c、 init(Cipher.ENCRYPT_模式,keySpec,ivSpec);
c、 doFinal(明文,0,256,密文,0);
//解密,自己实现CTR模式
密码b=Cipher.getInstance(“AES/ECB/NoPadding”);
b、 init(Cipher.ENCRYPT_模式,keySpec);
用于(int block=0;block<16;block++){
字节[]iv=ivSpec.getIV();
整数进位=0;
字节[]块字节=新字节[16];
对于(inti=0;i<4;i++)块字节[15-i]=(字节)(块>>8*i);
对于(int i=15;i>=0;i--){
整数和=(iv[i]&255)+(blockbytes[i]&255)+进位;
iv[i]=(字节)和;
进位=和>>8;
}
b、 doFinal(iv,0,16,iv,0);
对于(inti=0;i<16;i++)明文[block*16+i]=(字节)(密文[block*16+i]^iv[i]);
}
//检查一下
对于(inti=0;i<256;i++)断言明文[i]==(字节)i;
}
}
(我知道格式不好,但请回答我自己的问题,以便为其他人记录)

正在更新计数器。。。 AES/CTR-BE的解释 这是基本思想。如果你读了作者对我问题的回答,你会发现IV基本上是:

<8 bytes nonce><8 bytes counter>
然后,当它到达第二个块时,它会将其更新为

0x0 0x0 0x0 0x2
依此类推,从技术上讲,它可以溢出到nonce中,但首先不建议加密那么多数据

现在我个人随机创建nonce/计数器。因此,猜测变得更加困难,这不是一个要求

上面所做的是允许您更新
计数器
,以确定要进入计数器的块数,不管您是从0x1开始还是从任何其他计数器值(rand)开始
public static byte[] update_iv(byte iv[], long blocks) {
    ByteBuffer buf = ByteBuffer.wrap(iv);
    buf.order(ByteOrder.BIG_ENDIAN);
    long tblocks = buf.getLong(8);
    tblocks += blocks;
    buf.putLong(8, tblocks);

    return buf.array();
}
<8 bytes nonce><8 bytes counter>
0x0 0x0 0x0 0x1
0x0 0x0 0x0 0x2
c.update(new byte[count])
<16 bytes AES key>
<8 bytes nonce>
<8 bytes counter>
<8 bytes (long) block count>
<4 byte partial block count>
echo "1234567890ABCDEF" > file1
echo "0987654321ABCDEFGHIJKLMNOPQRSTUVWXYZ" > file2
cat file1 file2 > file3
javac AESTest.java # Compile the java file
java AESTest key file1 append.aes
java AESTest key file2 append.aes append 
java AESTest key file3 noappend.aes
import java.util.Random;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.lang.String;
import java.io.File;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class AESTest {

    public static byte[] update_iv(byte iv[], long blocks) {
        ByteBuffer buf = ByteBuffer.wrap(iv);
        buf.order(ByteOrder.BIG_ENDIAN);
        long tblocks = buf.getLong(8);
        tblocks += blocks;
        buf.putLong(8, tblocks);

        return buf.array();
    }

    public static void main(String args[]) throws Exception {
        if (args.length < 3) {
            System.out.println("Not enough parameters:");
            System.out.println("keyfile input output [append]");
            return;
        }


        File keyfile = new File(args[0]);
        DataInputStream key_in;
        DataOutputStream key_out;
        DataInputStream input = new DataInputStream(new FileInputStream(args[1]));
        DataOutputStream output = null;

        byte key[] = new byte[16 + 16];
        byte aeskey[] = new byte[16];
        byte iv[] = new byte[16];
        byte ivOrig[] = new byte[16];
        long blocks = 0;
        int count = 0;

        if (!keyfile.isFile()) {
            System.out.println("Creating new key");
            Random ranGen = new SecureRandom();
            ranGen.nextBytes(aeskey);
            ranGen.nextBytes(iv);

            iv = update_iv(iv, 0);

            System.arraycopy(iv, 0, ivOrig, 0, 16);
       } else {
            System.out.println("Using existing key...");
            key_in = new DataInputStream(new FileInputStream(keyfile));

            try {
                for (int i = 0; i < key.length; i++)
                    key[i] = key_in.readByte();
            } catch (EOFException e) {
            }

            System.arraycopy(key, 0, aeskey, 0, 16);
            System.arraycopy(key, 16, iv, 0, 16);
            System.arraycopy(key, 16, ivOrig, 0, 16);

            if (args.length == 4) {
                if (args[3].compareTo("append") == 0) {
                    blocks = key_in.readLong();
                    count = key_in.readInt();

                    System.out.println("Moving IV " + blocks + " forward");
                    iv = update_iv(iv, blocks);
                    output = new DataOutputStream(new FileOutputStream(args[2], true)); // Open file in append mode
                }
            }
        }

        if (output == null)
            output = new DataOutputStream(new FileOutputStream(args[2])); // Open file at the beginnging

        key_out = new DataOutputStream(new FileOutputStream(keyfile));

        Cipher c = Cipher.getInstance("AES/CTR/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec(aeskey, "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        c.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

        if (count != 0) {
            c.update(new byte[count]);
        }

        byte cc[] = new byte[1];
        try {
            while (true) {
                cc[0] = input.readByte();
                cc = c.update(cc);
                output.writeByte(cc[0]);

                if (count == 15) {
                    blocks++;
                    count = 0;
                } else {
                    count++;
                }
            }
        } catch (EOFException e) {
        }

        cc = c.doFinal();
        if (cc.length != 0)
            output.writeByte(cc[0]);

        // Before we quit, lets write our AES key, start IV, and current IV to disk
        for (int i = 0; i < aeskey.length; i++)
            key_out.writeByte(aeskey[i]);

        for (int i = 0; i < ivOrig.length; i++)
            key_out.writeByte(ivOrig[i]);


        System.out.println("Blocks: " + blocks);
        System.out.println("Extra: " + count);
        key_out.writeLong(blocks);
        key_out.writeInt(count);

    }
}