【问题标题】:Can anyone tell me why I can't use Padding with this AES implementation?谁能告诉我为什么我不能在这个 AES 实现中使用 Padding?
【发布时间】:2013-06-24 15:31:52
【问题描述】:

我正在尝试编写一个应用程序,用户应该能够从算法操作模式(CBC、CTR ...)中进行选择

我正在使用此代码(不是我编写的)进行 AES 加密,但我注意到我只能将它与 NoPadding 一起使用,我不明白为什么。

public void setPassword(String password) throws UnsupportedEncodingException {
    this.password = password.getBytes("UTF-16LE");
    debug("Using password: ", this.password);
}

public void encrypt(int version, String fromPath, String toPath) throws IOException, GeneralSecurityException {
    InputStream in = null;
    OutputStream out = null;
    try {
        in = new BufferedInputStream(new FileInputStream(fromPath));
        debug("Opened for reading: " + fromPath);
        out = new BufferedOutputStream(new FileOutputStream(toPath));
        debug("Opened for writing: " + toPath);

        encrypt(version, in, out);
    } finally {
        if (in != null) {
            in.close();
        }
        if (out != null) {
            out.close();
        }
    }
}

public void encrypt(int version, InputStream in, OutputStream out) throws IOException, GeneralSecurityException {
    try {
        byte[] text = null;

        ivSpec1 = new IvParameterSpec(generateIv1());
        aesKey1 = new SecretKeySpec(generateAESKey1(ivSpec1.getIV(), password), CRYPT_ALG);
        ivSpec2 = new IvParameterSpec(generateIV2());
        aesKey2 = new SecretKeySpec(generateAESKey2(), CRYPT_ALG);
        debug("IV1: ", ivSpec1.getIV());
        debug("AES1: ", aesKey1.getEncoded());
        debug("IV2: ", ivSpec2.getIV());
        debug("AES2: ", aesKey2.getEncoded());

        out.write("AES".getBytes("UTF-8")); // Heading.
        out.write(version); // Version.
        out.write(0); // Reserved.
        if (version == 2) { // No extensions.
            out.write(0);
            out.write(0);
        }
        out.write(ivSpec1.getIV()); // Initialization Vector.

        text = new byte[BLOCK_SIZE + KEY_SIZE];
        cipher.init(Cipher.ENCRYPT_MODE, aesKey1, ivSpec1);
        cipher.update(ivSpec2.getIV(), 0, BLOCK_SIZE, text);
        cipher.doFinal(aesKey2.getEncoded(), 0, KEY_SIZE, text, BLOCK_SIZE);
        out.write(text); // Crypted IV and key.
        debug("IV2 + AES2 ciphertext: ", text);

        hmac.init(new SecretKeySpec(aesKey1.getEncoded(), HMAC_ALG));
        text = hmac.doFinal(text);
        out.write(text); // HMAC from previous cyphertext.
        debug("HMAC1: ", text);

        cipher.init(Cipher.ENCRYPT_MODE, aesKey2, ivSpec2);
        hmac.init(new SecretKeySpec(aesKey2.getEncoded(), HMAC_ALG));
        text = new byte[BLOCK_SIZE];
        int len, last = 0;
        while ((len = in.read(text)) > 0) {
            cipher.update(text, 0, BLOCK_SIZE, text);
            hmac.update(text);
            out.write(text); // Crypted file data block.
            last = len;
        }
        last &= 0x0f;
        out.write(last); // Last block size mod 16.
        debug("Last block size mod 16: " + last);

        text = hmac.doFinal();
        out.write(text); // HMAC from previous cyphertext.
        debug("HMAC2: ", text);
    } catch (InvalidKeyException e) {
        throw new GeneralSecurityException(JCE_EXCEPTION_MESSAGE, e);
    }
}

public void decrypt(String fromPath, String toPath) throws IOException, GeneralSecurityException {
    InputStream in = null;
    OutputStream out = null;
    try {
        in = new BufferedInputStream(new FileInputStream(fromPath));
        debug("Opened for reading: " + fromPath);
        out = new BufferedOutputStream(new FileOutputStream(toPath));
        debug("Opened for writing: " + toPath);

        decrypt(new File(fromPath).length(), in, out);
    } finally {
        if (in != null) {
            in.close();
        }
        if (out != null) {
            out.close();
        }
    }
}

public void decrypt(long inSize, InputStream in, OutputStream out) throws IOException, GeneralSecurityException {
    try {
        byte[] text = null, backup = null;
        long total = 3 + 1 + 1 + BLOCK_SIZE + BLOCK_SIZE + KEY_SIZE + SHA_SIZE + 1 + SHA_SIZE;
        int version;

        text = new byte[3];
        readBytes(in, text); // Heading.
        if (!new String(text, "UTF-8").equals("AES")) {
            throw new IOException("Invalid file header");
        }

        version = in.read(); // Version.
        if (version < 1 || version > 2) {
            throw new IOException("Unsupported version number: " + version);
        }
        debug("Version: " + version);

        in.read(); // Reserved.

        if (version == 2) { // Extensions.
            text = new byte[2];
            int len;
            do {
                readBytes(in, text);
                len = ((0xff & (int) text[0]) << 8) | (0xff & (int) text[1]);
                if (in.skip(len) != len) {
                    throw new IOException("Unexpected end of extension");
                }
                total += 2 + len;
                debug("Skipped extension sized: " + len);
            } while (len != 0);
        }

        text = new byte[BLOCK_SIZE];
        readBytes(in, text); // Initialization Vector.
        ivSpec1 = new IvParameterSpec(text);
        aesKey1 = new SecretKeySpec(generateAESKey1(ivSpec1.getIV(), password), CRYPT_ALG);
        debug("IV1: ", ivSpec1.getIV());
        debug("AES1: ", aesKey1.getEncoded());

        cipher.init(Cipher.DECRYPT_MODE, aesKey1, ivSpec1);
        backup = new byte[BLOCK_SIZE + KEY_SIZE];
        readBytes(in, backup); // IV and key to decrypt file contents.
        debug("IV2 + AES2 ciphertext: ", backup);
        text = cipher.doFinal(backup);
        ivSpec2 = new IvParameterSpec(text, 0, BLOCK_SIZE);
        aesKey2 = new SecretKeySpec(text, BLOCK_SIZE, KEY_SIZE, CRYPT_ALG);
        debug("IV2: ", ivSpec2.getIV());
        debug("AES2: ", aesKey2.getEncoded());

        hmac.init(new SecretKeySpec(aesKey1.getEncoded(), HMAC_ALG));
        backup = hmac.doFinal(backup);
        text = new byte[SHA_SIZE];
        readBytes(in, text); // HMAC and authenticity test.
        if (!Arrays.equals(backup, text)) {
            throw new IOException("Message has been altered or password incorrect");
        }
        debug("HMAC1: ", text);

        total = inSize - total; // Payload size.
        if (total % BLOCK_SIZE != 0) {
            throw new IOException("Input file is corrupt");
        }
        if (total == 0) { // Hack: empty files won't enter block-processing
                          // for-loop below.
            in.read(); // Skip last block size mod 16.
        }
        debug("Payload size: " + total);

        cipher.init(Cipher.DECRYPT_MODE, aesKey2, ivSpec2);
        hmac.init(new SecretKeySpec(aesKey2.getEncoded(), HMAC_ALG));
        backup = new byte[BLOCK_SIZE];
        text = new byte[BLOCK_SIZE];
        for (int block = (int) (total / BLOCK_SIZE); block > 0; block--) {
            int len = BLOCK_SIZE;
            if (in.read(backup, 0, len) != len) { // Cyphertext block.
                throw new IOException("Unexpected end of file contents");
            }
            cipher.update(backup, 0, len, text);
            hmac.update(backup, 0, len);
            if (block == 1) {
                int last = in.read(); // Last block size mod 16.
                debug("Last block size mod 16: " + last);
                len = (last > 0 ? last : BLOCK_SIZE);
            }
            out.write(text, 0, len);
        }
        out.write(cipher.doFinal());

        backup = hmac.doFinal();
        text = new byte[SHA_SIZE];
        readBytes(in, text); // HMAC and authenticity test.
        if (!Arrays.equals(backup, text)) {
            throw new IOException("Message has been altered or password incorrect");
        }
        debug("HMAC2: ", text);
    } catch (InvalidKeyException e) {
        throw new GeneralSecurityException(JCE_EXCEPTION_MESSAGE, e);
    }
}

当我尝试使用 PCS5Padding 加密文件时,出现以下错误:

Output buffer too short: 32 bytes given, 48 bytes needed 
javax.crypto.ShortBufferException: Output buffer too short: 32 bytes given, 48 bytes needed

【问题讨论】:

  • 那是一大堆代码。请您将其简化为重现您的问题所需的最少代码。哦,你能告诉我们当你尝试使用填充时实际发生了什么吗?
  • 不要允许ECB模式,这是不安全的。使用最少的选项将您的应用程序缩减为仅 CBC 模式,并使其正常工作。只有当最小应用程序正常工作时,您才应该添加更多选项。
  • @rossum 我不使用欧洲央行,它甚至无法使用它。我第一次写问题时犯了一个错误。
  • @Duncan Jones 我忘了写我得到的错误。

标签: java encryption aes padding


【解决方案1】:

你得到了那个异常,因为正如消息所说,输出缓冲区太短了。

我不知道在哪一行引发了异常(您没有发布堆栈跟踪),但似乎代码只处理特定长度的密文,并且在应用填充时失败(如 PKCS# 5个padding可以加一整块)。

加密“主”加密密钥有什么意义?它不会增加任何安全性,但更复杂。根据古老的格言,复杂性是安全的敌人。

还要注意自制(或从互联网上复制粘贴)密码术的危险。我可以立即发现至少一个漏洞——MAC-then-pad 序列允许 padding oracle 攻击。最好使用久经考验的解决方案,例如 Keyczar

【讨论】:

  • 谢谢!我自己不太了解如何编写它,而且我面临时间压力。我编写了界面,它可以满足我的需要,但我很好奇为什么我不能使用填充。你的解释似乎足够好。你知道像 Keyczar 这样也有 DES 加密的东西吗?
  • 您从纯文本开始,然后添加填充。你加密的是明文+填充。因此,您的输出缓冲区需要有足够的空间来容纳两者。由于始终添加填充,因此您将始终获得比初始明文更长的加密输出。
  • @StoicaDan 如果 Keyczar 支持 DES,我会非常非常惊讶。 DES 密钥非常脆弱,以至于在现代家用计算机上,它们可以在几个小时内被暴力破解。
  • @ntoskrnl 我知道 DES 很弱,但我想做的是一个允许用户选择他们想要使用的加密算法的应用程序。
  • @StoicaDan 这有什么意义?依靠单一的已知工作算法(即 AES)是一个更好的主意,特别是考虑到大多数用户对密码学一无所知并且无法自行选择合理组合的事实。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-09
  • 2013-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-08
相关资源
最近更新 更多