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


116

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

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

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();
  }
}

48
심각한 프로젝트에서이 질문에 대한 답을 사용하지 마십시오! 이 질문에 제공된 모든 예제는 패딩 오라클에 취약하며 전반적으로 매우 나쁜 암호화 사용입니다. 아래 스 니펫 중 하나를 사용하여 프로젝트에 심각한 암호화 취약성을 도입 할 수 있습니다.
HoLyVieR

16
@HoLyVieR, "자신의 암호화 라이브러리를 개발해서는 안됩니다.""프레임 워크가 제공하는 높은 수준의 API를 사용하십시오." 여기 아무도 자신의 암호화 라이브러리를 개발하지 않습니다. 우리는 단순히 자바 프레임 워크가 제공하는 기존의 높은 수준의 API를 사용하고 있습니다. 당신은 매우 부정확합니다.
k170

10
@MaartenBodewes, 둘 다 동의한다고해서 둘 다 옳다는 것을 의미하지는 않습니다. 좋은 개발자는 높은 수준의 API를 래핑하는 것과 낮은 수준의 API를 다시 작성하는 것의 차이점을 알고 있습니다. 좋은 독자들은 OP가 "간단한 자바 AES 암호화 / 복호화 예제"를 요청했고 그것이 정확히 그가 얻은 것임을 알게 될 것 입니다. 나는 또한 다른 답변에 동의하지 않기 때문에 내 답변을 게시했습니다. 아마도 여러분도 똑같이 시도하고 여러분의 전문 지식으로 우리 모두를 계몽해야 할 것입니다.
k170

6
@HoLyVieR 정말 내가 지금까지 읽은 것 중 가장 터무니없는 것입니다! 사람들이 개발할 수있는 것과 개발할 수없는 것을 말하는 사람은 누구입니까?
TedTrippin

14
여전히 @HoLyVieR의 예가 없습니다. 라이브러리에 대한 포인터를 보시겠습니까? 전혀 건설적이지 않습니다.
danieljimenez

답변:


245

저를 포함한 많은 사람들이 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")));
    }
}

47
타사 Apache Commons Codec 라이브러리에 의존하지 않으려면 JDK의 javax.xml.bind.DatatypeConverter 를 사용하여 Base64 인코딩 / 디코딩을 수행 할 수 있습니다 . System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0

8
상수 IV를 사용하고 있습니까?!
vianna77

36
Java 8에는 이미 Base64 도구가 있습니다. java.util.Base64.getDecoder () 및 java.util.Base64.getEncoder ()
Hristo Stoyanov

11
IV는 비밀 일 필요는 없지만 CBC 모드에서는 예측할 수없고 CTR에 대해 고유해야합니다. 암호문과 함께 보낼 수 있습니다. 이를 수행하는 일반적인 방법은 IV를 암호문 앞에 접두사로 붙이고 해독하기 전에 잘라내는 것입니다. 생성되어야합니다SecureRandom
Artjom B.

6
암호는 열쇠가 아닙니다. IV는 무작위이어야합니다.
Maarten Bodewes 2016 년

40

여기에없는 솔루션 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!

5
이것은 @chandpriyankara와 마찬가지로 완벽하게 기능적인 예입니다. 그러나 왜의 서명을 정의 encrypt(String)하지 encrypt(byte[] )?. 암호화 (복호화도 마찬가지)는 바이트 기반 프로세스입니다 (AES도 마찬가지입니다). 암호화는 바이트를 입력으로 취하고 바이트를 출력하므로 복호화도 마찬가지입니다 (예 : Cipher객체가 수행합니다). 이제 한 가지 특별한 사용 사례는 문자열에서 오는 암호화 된 바이트를 갖거나 문자열 (메일의 경우 base64 MIME 첨부 파일 ...)로 전송되는 것일 수 있지만, 이는 인코딩 바이트의 문제입니다. AES / 암호화와 전혀 관련이없는 솔루션입니다.
GPI

3
@GPI : 예,하지만 Strings기본적으로 95 %의 시간 동안 작업하고 결국 변환을 수행하기 때문에 더 유용 합니다.
BullyWiiPlaza 2015

9
아니요, 이것은 chandpriyankara의 코드와 동일하지 않습니다! 귀하의 코드는 일반적으로 안전하지 않고 원하지 않는 ECB를 사용합니다. CBC를 명시 적으로 지정해야합니다. CBC가 지정되면 코드가 손상됩니다.
Dan

완벽하게 기능하고 완전히 안전하지 않으며 매우 나쁜 프로그래밍 관행을 사용합니다. 클래스 이름이 잘못되었습니다. 키 크기는 미리 확인하지 않습니다. 그러나 가장 중요한 것은 코드가 안전하지 않은 ECB 모드를 사용 하여 원래 질문에 문제를 숨 깁니다 . 마지막으로 문자 인코딩을 지정하지 않으므로 다른 플랫폼에서는 텍스트 디코딩이 실패 할 수 있습니다.
Maarten Bodewes

24

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의 필요성을 피하는 방법을 알고 있지 않습니까? 나는 그것을 사용하지 않는 다른 AES 예제를 보았지만 시도하지는 않았습니다.
TedTrippin 2013 년

무시하세요. 답을 찾았습니다! AES / ECB / PKCS5Padding을 사용해야합니다.
TedTrippin 2013 년

20
대부분의 시간은 당신이 하지 않는 ECB를 사용하고 싶습니다. 왜 구글링을했는지.
João Fernandes

2
@Mushy : 신뢰할 수있는 임의 소스에서 IV를 선택하고 명시 적으로 설정하는 것이 Cihper 인스턴스가 하나를 선택하도록하는 것보다 낫다는 데 동의했습니다. 반면 에이 답변은 키에 대한 초기화 벡터를 혼동하는 원래 문제를 해결합니다. 그것이 처음에 찬성 된 이유입니다. 이제이 게시물은 샘플 코드의 요점이되었으며 여기에있는 사람들은 원래 질문에 대한 내용 외에 몇 가지 훌륭한 예를 만들었습니다.
GPI

3
@GPI Upvoted. 다른 "훌륭한 예"는 그다지 크지 않으며 실제로 질문을 전혀 다루지 않습니다. 대신 여기는 보안 문제가있을 수 있다는 사실을 이해하지 못한 채 초보자가 암호화 샘플을 맹목적으로 복사 할 수있는 장소 인 것 같습니다.
Maarten Bodewes

17

암호 해독에 사용하는 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는 인코딩과 관련이 없으므로 타사 라이브러리없이 별도로 처리하기로 선택한 이유입니다.


우선, 당신은 원래 질문에 대답하지 않았습니다. 둘째, 왜 이미 답변되고 잘 받아 들여진 질문에 대답합니까? 보호 기능이이 스팸을 막아야한다고 생각했습니다.
TedTrippin

14
수락 된 답변과 마찬가지로 나는 예를 통해 귀하의 질문에 대답하기로 결정했습니다. 나는 무엇보다도 초기화 벡터를 적절히 처리하는 방법을 보여주는 완전한 기능의 코드를 제공했습니다. 두 번째 질문에 대해서는 Apache 코덱이 더 이상 필요하지 않기 때문에 업데이트 된 답변이 필요하다고 느꼈습니다. 그래서 이것은 스팸이 아닙니다. 그만해
k170

7
IV는 것입니다 특정 목적이 암호문을 무작위 및 의미 보안을 제공합니다. 동일한 키 + IV 쌍을 사용하면 공격자가 이전과 동일한 접두사로 메시지를 보냈는지 여부를 확인할 수 있습니다. IV는 비밀 일 필요는 없지만 예측할 수 없어야합니다. 일반적인 방법은 단순히 IV를 암호문에 접두사로 붙이고 해독하기 전에 잘라내는 것입니다.
Artjom B.

4
downvote : 하드 코딩 IV, 나쁜 이유 위 Artjom B. 코멘트를 참조
Murmel

1
CTR 모드는 NoPadding과 쌍을 이루어야합니다. CBC 대신 CTR 모드가 반드시 필요하지는 않지만 (패딩 오라클이 적용되지 않는 한) CTR 사용되는 경우 "/NoPadding". CTR은 스트림 암호에서 AES를 전환하는 모드이며 스트림 암호는 블록 대신 바이트에서 작동합니다.
Maarten Bodewes

16

이 답변에서 나는 이것이 대부분의 독자에게 이익이 될 것이라고 생각하기 때문에 특정 디버깅 질문이 아닌 "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를 구현하는 방법에 대한이 문서를 참조하십시오 .


문제는 예제를 요청하는 것이 명시 적으로 주제에서 벗어난 것입니다. 그리고 더 큰 문제는 이것이 검증하기 어려운 검토되지 않은 코드 조각이라는 것입니다. 노력은 감사하지만 SO가이를위한 장소가되어야한다고 생각하지 않습니다.
Maarten Bodewes

1
저는 그 노력에 감탄합니다. 그래서 한 가지 실수를 지적하겠습니다. "iv는 고유 한 것과 결합하여 예측할 수 없어야합니다 (즉, 임의의 iv 사용)"-이것은 CBC 모드에는 해당되지만 GCM에는 해당되지 않습니다.
Maarten Bodewes

this is true for CBC mode but not for GCM전체 부분을 의미합니까, 아니면 실제로 예측 불가능할 필요가없는 것입니까?
Patrick Favre

1
"주제를 이해하지 못한다면 처음부터 저수준 프리미티브를 사용하지 말아야합니다."확실히 그렇습니다. 많은 개발자들이 여전히 사용하고 있습니다. 보안 / 암호화와 관련된 고품질 콘텐츠를 자주 사용하지 않는 곳에 게시하는 것을 자제하는 것이 올바른 솔루션인지 잘 모르겠습니다. -내 실수를 가리키는 thx btw
Patrick Favre

1
좋아, 내가 (목적보다는) 대답 wrt 내용을 좋아하기 때문에 : IV 처리는 특히 복호화 중에 단순화 될 수 있습니다. Java는 결국 기존 바이트 배열에서 직접 IV를 쉽게 만들 수 있습니다. 오프셋 0에서 시작할 필요가없는 복호화도 마찬가지입니다.이 모든 복사는 단순히 필요하지 않습니다. 또한 IV에 대한 길이를 보내야 한다면 (그렇습니까?) 단일 (부호없는) 바이트를 사용하지 않는 이유는 무엇입니까? IV에 대해 255 바이트를 넘지 않을 것입니다.
Maarten Bodewes

2

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

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")));
    }
}

시원하고 행복합니다!
Bhupesh Pant 2010

암호는 키 가 아니며 IV는 정적이어서는 안됩니다. 여전히 문자열 형식의 코드이므로 키를 파괴 할 수 없습니다. IV로 무엇을 해야할지, 예측할 수 없다는 개념도 없습니다.
Maarten Bodewes

1

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

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 답변을 기반으로 합니다.


5
ECB 모드를 사용하지 마십시오. 기간.
Konstantino Sparakis

1
동일한 키로 둘 이상의 데이터 블록을 암호화하는 경우 ECB를 사용해서는 안되므로 "인코딩 할 텍스트"로 충분합니다. stackoverflow.com/a/1220869/146745
andrej

) aesKey = keygen.generateKey (: @AndroidDev 키 키부 준비 생성된다
안드레이

1

이것은 받아 들여진 대답에 비해 개선 된 것입니다.

변경 사항 :

(1) 임의의 IV를 사용하여 암호화 된 텍스트 앞에 추가

(2) SHA-256을 사용하여 암호에서 키 생성

(3) Apache Commons에 의존하지 않음

public static void main(String[] args) throws GeneralSecurityException {
    String plaintext = "Hello world";
    String passphrase = "My passphrase";
    String encrypted = encrypt(passphrase, plaintext);
    String decrypted = decrypt(passphrase, encrypted);
    System.out.println(encrypted);
    System.out.println(decrypted);
}

private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}

private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
    return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}

public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
    byte[] initVector = new byte[16];
    SecureRandom.getInstanceStrong().nextBytes(initVector);
    Cipher cipher = getCipher();
    cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] encrypted = cipher.doFinal(value.getBytes());
    return DatatypeConverter.printBase64Binary(initVector) +
            DatatypeConverter.printBase64Binary(encrypted);
}

public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
    byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
    Cipher cipher = getCipher();
    cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
    return new String(original);
}

해시는 여전히 암호 기반 키 생성 기능 / PBKDF가 아닙니다. 무작위 키를 사용하거나 PBKDF2 / 비밀번호 기반 암호화와 같은 PBKDF를 사용합니다.
Maarten Bodewes

@MaartenBodewes 개선을 제안 할 수 있습니까?
wvdz

PBKDF2는 Java에 있으므로 방금 제안한 것 같습니다. 좋아요, 저는 코딩을 하지 않았지만 제 생각에는 너무 많은 것을 요구하고 있습니다. 암호 기반 암호화의 예가 많이 있습니다.
Maarten Bodewes

@MaartenBodewes 나는 그것이 간단한 수정이라고 생각했습니다. 호기심에서이 코드를있는 그대로 사용할 때 특정 취약점은 무엇입니까?
wvdz

0

Spring Boot와 함께 java.util.Base64를 사용하는 또 다른 솔루션

암호화 기 클래스

package com.jmendoza.springboot.crypto.cipher;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Component
public class Encryptor {

    @Value("${security.encryptor.key}")
    private byte[] key;
    @Value("${security.encryptor.algorithm}")
    private String algorithm;

    public String encrypt(String plainText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
    }

    public String decrypt(String cipherText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }
}

EncryptorController 클래스

package com.jmendoza.springboot.crypto.controller;

import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cipher")
public class EncryptorController {

    @Autowired
    Encryptor encryptor;

    @GetMapping(value = "encrypt/{value}")
    public String encrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.encrypt(value);
    }

    @GetMapping(value = "decrypt/{value}")
    public String decrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.decrypt(value);
    }
}

application.properties

server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0

http : // localhost : 8082 / cipher / encrypt / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

http : // localhost : 8082 / cipher / decrypt / 2h41HH8Shzc4BRU3hVDOXA ==

jmendoza


-1

수락 된 답변의 최적화 된 버전입니다.

  • 타사 라이브러리 없음

  • 암호화 된 메시지에 IV 포함 (공개 가능)

  • 암호는 길이 제한이 없습니다.

암호:

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;

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

public class Encryptor {
    public static byte[] getRandomInitialVector() {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
            byte[] initVector = new byte[cipher.getBlockSize()];
            randomSecureRandom.nextBytes(initVector);
            return initVector;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static byte[] passwordTo16BitKey(String password) {
        try {
            byte[] srcBytes = password.getBytes("UTF-8");
            byte[] dstBytes = new byte[16];

            if (srcBytes.length == 16) {
                return srcBytes;
            }

            if (srcBytes.length < 16) {
                for (int i = 0; i < dstBytes.length; i++) {
                    dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
                }
            } else if (srcBytes.length > 16) {
                for (int i = 0; i < srcBytes.length; i++) {
                    dstBytes[i % dstBytes.length] += srcBytes[i];
                }
            }

            return dstBytes;
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String encrypt(String key, String value) {
        return encrypt(passwordTo16BitKey(key), value);
    }

    public static String encrypt(byte[] key, String value) {
        try {
            byte[] initVector = Encryptor.getRandomInitialVector();
            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

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

            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String encrypted) {
        return decrypt(passwordTo16BitKey(key), encrypted);
    }

    public static String decrypt(byte[] key, String encrypted) {
        try {
            String[] encryptedParts = encrypted.split(" ");
            byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
            if (initVector.length != 16) {
                return null;
            }

            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

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

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

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

        return null;
    }
}

용법:

String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);

출력 예 :

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World

암호 파생 기능이 안전하지 않습니다. 나는 e.printStackTrace()소위 최적화 된 코드에서 기대하지 않을 것이다 .
Maarten Bodewes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.