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 2016

16
@HoLyVieR、次の引用について:「独自の暗号化ライブラリを開発するべきではない」「フレームワークが提供する高レベルのAPIを使用する」。ここでは、独自の暗号化ライブラリを開発している人はいません。Javaフレームワークが提供する既存の高レベルAPIを単に使用しています。あなたは非常に不正確です。
k170

10
@MaartenBodewes、両方が同意するからといって、両方が正しいことを意味するものではありません。優れた開発者は、高レベルAPIのラップと低レベルAPIの書き換えの違いを知っています。良い読者は、OPが「単純なJava AES暗号化/復号化の例」を要求したことに気づくでしょうそれがまさに彼が得たものです。また、他の回答にも同意しません。そのため、自分の回答を投稿しました。おそらく、皆さんも同じことを試して、あなたの専門知識で私たち全員を啓発する必要があります。
k170 2016

6
@HoLyVieRそれは本当に私が今までに読んだ中で最も馬鹿げたことです!何を開発でき、何を開発できないかを誰に伝えますか?
TedTrippin 2017

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のコーデックライブラリに依存したくない場合は、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 2016年

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 Codec「S Base64

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オブジェクトが行います)。現在、特定の使用例の1つは、文字列から暗号化されたバイトを送信したり、文字列(メールのbase64 MIME添付ファイル)として送信したりすることですが、これはバイトのエンコードの問題であり、何百もの問題があります。ソリューション、AES /暗号化とはまったく関係ありません。
GPI 2015

3
@GPI:はい、しかし私はそれがStrings基本的に私が95%の時間で作業するものであり、あなたはとにかく変換​​してしまうので、私はそれがより便利だと思います。
BullyWiiPlaza

9
いいえ、これはchandpriyankaraのコードと同等ではありません。コードがECBを使用していますが、これは一般的に安全ではなく、望まれていません。CBCを明示的に指定する必要があります。CBCを指定すると、コードが壊れます。
Dan

完全に機能し、まったく安全ではなく、非常に悪いプログラミング方法を使用しています。クラスの名前が間違っています。鍵のサイズは事前に確認されていません。ただし、最も重要なのは、コードが安全でないECBモードを使用し、元の質問に問題が隠されていることです。最後に、文字エンコードは指定されていません。つまり、他のプラットフォームではテキストへのデコードが失敗する可能性があります。
Maarten Bodewes

24

初期化ベクトル(IV)を適切に処理していないようです。私が最後にAES、IV、ブロックチェーンについて読んだのは久しぶりですが、あなたのライン

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

大丈夫ではないようです。AESの場合、初期化ベクトルは暗号インスタンスの「初期状態」と考えることができます。この状態は、キーからではなく、暗号化暗号の実際の計算から取得できる少しの情報です。(IVをキーから抽出できたとしても、キーはすでに初期化フェーズ中に暗号インスタンスに与えられているため、役に立たないと主張することができます)。

したがって、暗号化の最後に、暗号インスタンスからバイト[]としてIVを取得する必要があります

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

そして、あなたはこのbyte [] Cipherでin DECRYPT_MODEを初期化する必要があります:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

その後、復号化はOKです。お役に立てれば。


初心者を助けてくれてありがとう。私は他の投稿からこの例を作りました。IVの必要性を回避する方法を知っていると思いませんか?私はそれを使用しない他のAESの例を見ましたが、試していません。
TedTrippin 2013年

それを無視して、私は答えを見つけました!AES / ECB / PKCS5Paddingを使用する必要があります。
TedTrippin 2013年

20
ほとんどの場合、ECBを使用する必要はありません。理由をググってください。
ジョアン・フェルナンデス

2
@Mushy:信頼できるランダムソースからIVを選択して明示的に設定する方が、Cihperインスタンスにそれを選択させるだけの場合よりも優れていることに同意しました。一方、この回答は、キーの初期化ベクトルを混乱させるという最初の問題に対処します。それが最初に賛成された理由です。さて、この投稿はサンプルコードの要点になり、元の質問が何であったかを除いて、ここの人々はいくつかの素晴らしい例を作りました。
GPI

3
@GPI賛成。他の「素晴らしい例」はそれほどすばらしいものではなく、実際にはまったく問題に対応していません。代わりに、これは初心者がセキュリティの問題の可能性があることを理解せずに暗号サンプルを盲目的にコピーする場所であったようです-そしていつものようにあります。
Maarten Bodewes 2016

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はエンコーディングとは何の関係もないことに注意してください。そのため、サードパーティのライブラリを必要とせずに個別に処理することにしました。


まず、元の質問に答えていません。第2に、すでに回答済みでよく受け入れられている質問に答える理由は何ですか。保護はこのスパムを阻止することになっていると思いました。
TedTrippin、2015年

14
受け入れられた答えのように、私は例を介してあなたの質問に答えることを選びました。特に、初期化ベクトルを適切に処理する方法を示す、完全に機能するコードを提供しました。2番目の質問については、Apacheコーデックが不要になったため、更新された回答が必要だと感じました。したがって、これはスパムではありません。trippinを停止します。
k170 2015年

7
アンIVはしている、特定の目的がある暗号文をランダム化し、セマンティックセキュリティを提供します。同じキーとIVのペアを使用すると、攻撃者は以前と同じプレフィックスでメッセージを送信したかどうかを判断できます。IVは秘密である必要はありませんが、予測不可能である必要があります。一般的な方法は、IVを単に暗号文の前に付け、復号化する前にそれを切り捨てることです。
Artjom B.

4
反対票:ハードコードされたIV、Artjom Bを参照してください。理由は上記のコメント
Murmel

1
CTRモードはNoPaddingとペアにする必要があります。CBCの代わりにCTRモードは必須ではありません(パディングオラクルが適用されない限り)。ただし、CTR 使用されている場合、を使用してください"/NoPadding"。CTRはストリーム暗号でAESを有効にするモードであり、ストリーム暗号はブロックではなくバイトで動作します。
Maarten Bodewes 2016

16

この回答では、特定のデバッグに関する質問ではなく、「単純なJava AES暗号化/復号化の例」のメインテーマにアプローチすることを選択します。

これは、JavaでのAES暗号化に関する私のブログ投稿の簡単な要約なので、何かを実装する前にそれを読むことをお勧めします。ただし、使用する簡単な例を提供し、注意点をいくつか示します。

この例では、ガロア/カウンターモードまたはGCMモードで認証済み暗号化を使用することを選択します。その理由は、ほとんどの場合、 整合性と信頼性を機密性と組み合わせて必要とするためですブログで詳細をご覧ください)。

AES-GCM暗号化/復号化チュートリアル

Java暗号化アーキテクチャ(JCA)使用した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データの大きなチャンクを暗号化するときに使用します
  • 変更された場合、追加の(非機密)データをチェックしたいですか?ここではMoreに関連データを使用することをお勧めします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でエンコードします。どちらの使用AndroidののJava 8つの内蔵実装(Apacheのコモンズのコーデックを使用していない-それはひどい実装です)。エンコーディングは、バイト配列を文字列表現に「変換」して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など)に比べて実装が簡単であり、実装が簡単なため、私はまだこのモードを選択しています。HESでAES-CBCを実装する方法については、この記事を参照してください


問題は、例を求めることがSOのトピックから明らかに外れていることです。さらに大きな問題は、これらは未確認のコードであり、検証が難しいことです。私は努力に感謝しますが、SOがこのための場所であるべきだとは思いません。
Maarten Bodewes

1
私はその努力に感心するので、「ivは一意であることと組み合わせて予測できない(つまり、ランダムivを使用する)」という1つの間違いを指摘します。これはCBCモードには当てはまりますが、GCMには当てはまりません。
Maarten Bodewes

this is true for CBC mode but not for GCM全体を意味するのか、それとも実際に予測不可能である必要がないのか?
Patrick Favre

1
「トピックを取得できない場合は、おそらく最初から低レベルのプリミティブを使用すべきではありません」確かにそうであるはずですが、多くの開発者はまだそうしています。セキュリティ/暗号化に関する高品質のコンテンツを、あまり多くない場所に配置することを控えるのはよくありません。-私の間違いを指摘するためのthx btw
Patrick Favre

1
わかりました、私は(目的ではなく)内容に対する回答が好きだからです。特に解読時に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")));
    }
}

クールで幸せになりました。
ブペシュパンツ2018

パスワードはキーではなく、IVは静的であってはなりません。それでも文字列型のコードで、キーを破棄することは不可能です。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暗号化アーキテクチャリファレンスガイドhttps://stackoverflow.com/a/20591539/146745回答に基づいています


5
ECBモードを使用しないでください。限目。
Konstantino Sparakis 2017年

1
同じキーで複数のデータブロックを暗号化する場合は、ECBを使用しないでください。そのため、「エンコードするテキスト」には十分です。stackoverflow.com/a/1220869/146745
andrej

@AndroidDevキーはキーの準備セクションで生成されます:aesKey = keygen.generateKey()
andrej

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に存在するので、提案したと思います。OK、私はコードを記述しませんでしたが、それは私の意見で少し多すぎることを求めています。パスワードベースの暗号化の例はたくさんあります。
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.