[java] Java AES / CBC 복호화 후 잘못된 초기 바이트

다음 예의 문제점은 무엇입니까?

문제는 해독 된 문자열의 첫 번째 부분이 말도 안된다는 것입니다. 그러나 나머지는 괜찮습니다.

Result: eB6OgeS��i are you? Have a nice day.
@Test
public void testEncrypt() {
  try {
    String s = "Hello there. How are you? Have a nice day.";

    // Generate key
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(128);
    SecretKey aesKey = kgen.generateKey();

    // Encrypt cipher
    Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

    // Encrypt
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
    cipherOutputStream.write(s.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();
    byte[] encryptedBytes = outputStream.toByteArray();

    // Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt
    outputStream = new ByteArrayOutputStream();
    ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
    CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
    byte[] buf = new byte[1024];
    int bytesRead;
    while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
        outputStream.write(buf, 0, bytesRead);
    }

    System.out.println("Result: " + new String(outputStream.toByteArray()));

  }
  catch (Exception ex) {
    ex.printStackTrace();
  }
}



답변

저를 포함한 많은 사람들이 Base64로 변환하는 것을 잊고 초기화 벡터, 문자 집합 등과 같은 정보가 누락되어이 작업을 수행하는 데 많은 문제에 직면합니다. 그래서 저는 완전한 기능의 코드를 만들 생각을했습니다.

이것이 여러분 모두에게 유용하길 바랍니다 : 컴파일하려면 추가 Apache Commons Codec jar가 필요합니다.
http://commons.apache.org/proper/commons-codec/download_codec.cgi

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string: "
                    + Base64.encodeBase64String(encrypted));

            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World")));
    }
}


답변

여기에없는 솔루션 Apache Commons CodecBase64:

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

public class AdvancedEncryptionStandard
{
    private byte[] key;

    private static final String ALGORITHM = "AES";

    public AdvancedEncryptionStandard(byte[] key)
    {
        this.key = key;
    }

    /**
     * Encrypts the given plain text
     *
     * @param plainText The plain text to encrypt
     */
    public byte[] encrypt(byte[] plainText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        return cipher.doFinal(plainText);
    }

    /**
     * Decrypts the given byte array
     *
     * @param cipherText The data to decrypt
     */
    public byte[] decrypt(byte[] cipherText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        return cipher.doFinal(cipherText);
    }
}

사용 예 :

byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
        encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);

System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));

인쇄물:

Hello world!
դ;��LA+�ߙb*
Hello world!


답변

IV (초기화 벡터)를 제대로 처리하지 않는 것 같습니다. AES, IV 및 블록 체인에 대해 마지막으로 읽은 지 오래되었지만

IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());

괜찮지 않은 것 같습니다. AES의 경우 초기화 벡터를 암호 인스턴스의 “초기 상태”로 생각할 수 있으며이 상태는 키에서 얻을 수 없지만 암호화 암호의 실제 계산에서 얻을 수있는 약간의 정보입니다. (키에서 IV를 추출 할 수 있다면 키가 초기화 단계에서 이미 암호 인스턴스에 주어 졌기 때문에 쓸모가 없을 것이라고 주장 할 수 있습니다.)

따라서 암호화가 끝날 때 암호 인스턴스에서 IV를 byte []로 가져와야합니다.

  cipherOutputStream.close();
  byte[] iv = encryptCipher.getIV();

당신은 당신의 초기화 할 필요가 Cipher있는을 DECRYPT_MODE] [이 바이트 :

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

그러면 암호 해독이 정상입니다. 도움이 되었기를 바랍니다.


답변

암호 해독에 사용하는 IV가 잘못되었습니다. 이 코드 교체

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

이 코드로

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

그리고 그것은 당신의 문제를 해결해야합니다.


다음은 Java로 된 간단한 AES 클래스의 예입니다. 이 클래스는 응용 프로그램의 모든 특정 요구 사항을 설명하지 못할 수 있으므로 프로덕션 환경에서는 사용하지 않는 것이 좋습니다.

import java.nio.charset.StandardCharsets;
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;
import java.util.Base64;

public class AES
{
    public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        byte[] transformedBytes = null;

        try
        {
            final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

            cipher.init(mode, keySpec, ivSpec);

            transformedBytes = cipher.doFinal(messageBytes);
        }
        catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e)
        {
            e.printStackTrace();
        }
        return transformedBytes;
    }

    public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        //Retrieved from a protected local file.
        //Do not hard-code and do not version control.
        final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";

        //Retrieved from a protected database.
        //Do not hard-code and do not version control.
        final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";

        //Extract the iv and the ciphertext from the shadow entry.
        final String[] shadowData = shadowEntry.split(":");
        final String base64Iv = shadowData[0];
        final String base64Ciphertext = shadowData[1];

        //Convert to raw bytes.
        final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
        final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);

        //Decrypt data and do something with it.
        final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);

        //Use non-blocking SecureRandom implementation for the new IV.
        final SecureRandom secureRandom = new SecureRandom();

        //Generate a new IV.
        secureRandom.nextBytes(ivBytes);

        //At this point instead of printing to the screen, 
        //one should replace the old shadow entry with the new one.
        System.out.println("Old Shadow Entry      = " + shadowEntry);
        System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
        System.out.println("New Shadow Entry      = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
    }
}

AES는 인코딩과 관련이 없으므로 타사 라이브러리없이 별도로 처리하기로 선택한 이유입니다.


답변

이 답변에서 나는 이것이 대부분의 독자에게 이익이 될 것이라고 생각하기 때문에 특정 디버깅 질문이 아닌 “Simple Java AES encrypt / decrypt example”기본 테마에 접근하기로 선택했습니다.

이것은 Java의 AES 암호화에 대한블로그 게시물 의 간단한 요약입니다. 이므로 구현하기 전에 읽어 보는 것이 좋습니다. 그러나 나는 여전히 사용하는 간단한 예제를 제공하고주의해야 할 몇 가지 지침을 제공 할 것입니다.

이 예에서 나는 사용을 선택합니다 인증 암호화를 함께 갈루아 / 카운터 모드 또는 GCM의 모드. 그 이유는 대부분의 경우 기밀성과 함께 무결성과 신뢰성 을 원하기 때문입니다 ( 블로그 에서 자세히 알아보기 ).

AES-GCM 암호화 / 복호화 자습서

다음은 JCA (Java Cryptography Architecture) 를 사용하여 AES-GCM 으로 암호화 / 복호화하는 데 필요한 단계 입니다. 다른 예와 혼합하지 마십시오미묘한 차이로 인해 코드가 완전히 안전하지 않을 수 있으므로 .

1. 키 생성

사용 사례에 따라 다르므로 가장 간단한 경우 인 임의의 비밀 키를 가정하겠습니다.

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");

중대한:

2. 초기화 벡터 생성

초기화 벡터 (IV)는 동일한 비밀 키가 다른 것이다 만들도록 사용되는 암호 텍스트를 .

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);

중대한:

3. IV 및 키로 암호화

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);

중대한:

  • 16 바이트 / 128 비트 인증 태그 사용 사용 (무결성 / 진위성을 확인하는 데 사용됨)
  • 인증 태그가 자동으로 암호 텍스트에 추가됩니다 (JCA 구현에서).
  • GCM은 스트림 암호처럼 작동하므로 패딩이 필요하지 않습니다.
  • 사용하다 CipherInputStream대량의 데이터를 암호화 할 때
  • 변경된 경우 추가 (비밀) 데이터를 확인 하시겠습니까? 여기 에서 더보기관련된 데이터 를 사용할 수 있습니다 cipher.updateAAD(associatedData); .

3. 단일 메시지로 직렬화

IV와 암호문 만 추가하면됩니다. 위에서 언급했듯이 IV는 비밀 일 필요가 없습니다.

ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();

문자열 표현이 필요한 경우 선택적으로 Base64로 인코딩 합니다. 어느 사용하는 안드로이드의 또는 자바 (8 개)의 내장 구현 (아파치 코 몬즈 코덱을 사용하지 마십시오 – 그것은 끔찍한 구현입니다). 인코딩은 바이트 배열을 문자열 표현으로 “변환”하여 ASCII 안전을 만드는 데 사용됩니다. 예 :

String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);

4. 해독 준비 : 역 직렬화

메시지를 인코딩 한 경우 먼저 바이트 배열로 디코딩합니다.

byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)

중대한:

5. 해독

암호를 초기화하고 암호화와 동일한 매개 변수를 설정합니다.

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);

중대한:

  • 암호화 중에 추가 한 경우 관련 데이터 를 추가하는 것을 잊지 마십시오 cipher.updateAAD(associatedData);.

이 요점에서 작동하는 코드 조각을 찾을 수 있습니다.


최신 Android (SDK 21+) 및 Java (7+) 구현에는 AES-GCM이 있어야합니다. 이전 버전에는 부족할 수 있습니다. 비슷한 모드 인 Encrypt-then-Mac (예 : AES-CBC + HMAC 사용 )에 비해 더 효율적일뿐만 아니라 구현하기가 더 쉽기 때문에 여전히이 모드를 선택합니다 . HMAC로 AES-CBC를 구현하는 방법에 대한이 문서를 참조하십시오 .


답변

온라인 편집기 실행 가능 버전 :-

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());

            //System.out.println("encrypted string: "
              //      + Base64.encodeBase64String(encrypted));

            //return Base64.encodeBase64String(encrypted);
            String s = new String(Base64.getEncoder().encode(encrypted));
            return s;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(encrypt(key, initVector, "Hello World"));
        System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
    }
}


답변

종종 표준 라이브러리 제공 솔루션에 의존하는 것이 좋습니다.

private static void stackOverflow15554296()
    throws
        NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException
{

    // prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecretKey aesKey = keygen.generateKey();
    String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
            aesKey.getEncoded()
    );

    // cipher engine
    Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

    // cipher input
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
    byte[] clearTextBuff = "Text to encode".getBytes();
    byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);

    // recreate key
    byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
    SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");

    // decipher input
    aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
    byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
    System.out.println(new String(decipheredBuff));
}

그러면 “인코딩 할 텍스트”가 인쇄됩니다.

솔루션은 Java Cryptography Architecture Reference Guidehttps://stackoverflow.com/a/20591539/146745 답변을 기반으로 합니다.