Java 为什么用 AES 加密 16 个字节时,密文长度为 32 个字节?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6486121/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 06:20:09  来源:igfitidea点击:

Why is the ciphertext 32 bytes long when encrypting 16 bytes with AES?

javaencryptionaes

提问by ebra

I use encryption AES algorithm, when i encrypt 16 byte(one block) the result is 32 byte. Is this ok?

我使用加密 AES 算法,当我加密 16 字节(一个块)时,结果是 32 字节。这个可以吗?

My source code that i used is:

我使用的源代码是:

package net.sf.andhsli.hotspotlogin;

import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * Usage:
 * <pre>
 * String crypto = SimpleCrypto.encrypt(masterpassword, cleartext)
 * ...
 * String cleartext = SimpleCrypto.decrypt(masterpassword, crypto)
 * </pre>
 * @author ferenc.hechler
 */
public class SimpleCrypto {

    public static String encrypt(String seed, String cleartext) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] result = encrypt(rawKey, cleartext.getBytes());
        return toHex(result);
    }

    public static String decrypt(String seed, String encrypted) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] enc = toByte(encrypted);
        byte[] result = decrypt(rawKey, enc);
        return new String(result);
    }

    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(seed);
        kgen.init(128, sr); // 192 and 256 bits may not be available
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }


    private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }

    private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

    public static String toHex(String txt) {
        return toHex(txt.getBytes());
    }
    public static String fromHex(String hex) {
        return new String(toByte(hex));
    }

    public static byte[] toByte(String hexString) {
        int len = hexString.length()/2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
        return result;
    }

    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2*buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }
    private final static String HEX = "0123456789ABCDEF";
    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
    }

}

回答by Morten Kristensen

If you look at the specification section 5then you can see that the input, outputand state are all 128 bit. The only thing that varies is the size of the key: 128, 196 or 256 bits. So encrypting a 16 byte input state will yield a 16 byte output state.

如果您查看规范第 5 节,则可以看到输入、输出和状态都是 128 位。唯一不同的是密钥的大小:128、196 或 256 位。所以加密一个 16 字节的输入状态将产生一个 16 字节的输出状态。

Are you sure you aren't mixing it up with the length in hexadecimal notation or similar? If it is in hexadecimal notation then it's correct because for each byte two characters are needed to represent it: 00-FF(for the range 0-255).

您确定没有将它与十六进制表示法或类似的长度混淆吗?如果它是十六进制表示法,那么它是正确的,因为对于每个字节需要两个字符来表示它:(00-FF对于范围0-255)。

Another way you can test if the encryption is correct is by doing the equivalent decryption, see if it matches the plaintext input string.

另一种可以测试加密是否正确的方法是进行等效解密,看看它是否与明文输入字符串匹配。

Anyway, it does the correct thing. Here's a test:

无论如何,它做正确的事。这是一个测试:

public static void main(String[] args) {
  try {
    String plaintext = "Hello world", key = "test";
    String ciphertext = encrypt(key, plaintext);
    String plaintext2 = decrypt(key, ciphertext);
    System.out.println("Encrypting '" + plaintext +
                       "' yields: (" + ciphertext.length() + ") " + ciphertext);
    System.out.println("Decrypting it yields: " + plaintext2);
  }
  catch (Exception ex) {
      ex.printStackTrace();
  }
}

Which yields:

其中产生:

Encrypting 'Hello world' yields: (32) 5B68978D821FCA6022D4B90081F76B4F

Decrypting it yields: Hello world

加密“Hello world”产生:(32) 5B68978D821FCA6022D4B90081F76B4F

解密它产生:Hello world

回答by Maarten Bodewes

AES defaults to ECB mode encryption with PKCS#7 compatible padding mode (for all providersobserved so far). ECB and CBC mode encryption require padding if the input is not precisely a multiple of the blocksize in size, with 16 being the block size of AES in bytes.

AES 默认使用 PKCS#7 兼容填充模式的 ECB 模式加密(对于目前观察到的所有提供者)。如果输入的大小不是块大小的精确倍数,则 ECB 和 CBC 模式加密需要填充,其中 16 是 AES 的块大小(以字节为单位)。

Unfortunately there might be no way for the unpadding mechanism to distinguish between padding and data; the data itself may represent valid padding. So for 16 bytes of input you will get another 16 bytes of padding. Padding modes that are deterministic such as PKCS#7 always padwith 1 to [blocksize] bytes.

不幸的是,unpadding 机制可能无法区分填充和数据;数据本身可能代表有效的填充。因此,对于 16 字节的输入,您将获得另外 16 字节的填充。确定性的填充模式(例如 PKCS#7)始终填充1 到 [blocksize] 字节。

If you look at int output = cipher.getOutputSize(16);you will get back 32 bytes. Use "AES/ECB/NoPadding"during decipher to see the padding bytes (e.g. 4D61617274656E20426F64657765732110101010101010101010101010101010).

如果你看一下,int output = cipher.getOutputSize(16);你会得到 32 个字节。"AES/ECB/NoPadding"在解密期间使用以查看填充字节(例如4D61617274656E20426F64657765732110101010101010101010101010101010)。

You are better off when you fully specify the algorithm. Previously most developers would go for "AES/CBC/PKCS5Padding"but nowadays "AES/GCM/NoPadding"should probably be used because it offers message authentication and integrity. Otherwise you will keep guessing which mode is actually used.

当您完全指定算法时,情况会更好。以前大多数开发人员会去,"AES/CBC/PKCS5Padding"但现在"AES/GCM/NoPadding"可能应该使用,因为它提供消息身份验证和完整性。否则,您将不断猜测实际使用的是哪种模式。

Note that using ECB mode is not safe as an attacker can retrieve information from the cipher text. Identical blocks of plain text encode to identical blocks of cipher text.

请注意,使用 ECB 模式并不安全,因为攻击者可以从密文中检索信息。相同的纯文本块编码为相同的密文块。

回答by Raja Rao

package com.cipher;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Encrypt {

    public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        // TODO Auto-generated method stub
String s="You are doing encryption at deep level";
SecureRandom sr=SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(s.getBytes());
byte[] k=new byte[128/8];
sr.nextBytes(k);
SecretKeySpec spec=new SecretKeySpec(k,"AES");
byte[] iv={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
IvParameterSpec ivs=new IvParameterSpec(iv);
Cipher cps=Cipher.getInstance("AES/CBC/PKCS5Padding");
cps.init(Cipher.ENCRYPT_MODE,spec,ivs);
byte[] iv2=cps.doFinal(s.getBytes());
System.out.println("En"+iv2);
Cipher cpr=Cipher.getInstance("AES/CBC/PKCS5Padding");
cpr.init(Cipher.DECRYPT_MODE, spec,ivs);
byte[] iv3=cpr.doFinal(iv2);
String ds=new String(iv3);
System.out.println(ds);


    }

}