Javaで文字列を暗号化する方法


150

私が必要なのは、2Dバーコード(PDF-417)に表示される文字列を暗号化することです。これにより、誰かがスキャンするアイデアを得たときに、何も読めなくなります。

その他の要件:

  • 複雑であってはならない
  • RSA、PKIインフラストラクチャ、キーペアなどで構成しないでください。

周囲を覗き見できるほど簡単で、そのデータの取得に関心のある他の企業にとって簡単に解読できる必要があります。彼らは私たちを呼んで、私たちは彼らに標準を伝えるか、それらに復号化に使用できるいくつかの単純なキーを与えます。

おそらくこれらの企業は異なるテクノロジーを使用する可能性があるため、特別なプラットフォームやテクノロジーに結び付けられていない標準に準拠するのが良いでしょう。

何を指示してるんですか?いくつかのJavaクラスがやっているencrypt()decrypt()、高いセキュリティ基準を達成するために多くの合併症なし?



警告。以下の回答の多くは、Javaであらゆる種類の暗号化を実行するための1つの方法または他の方法を示しています。回答適切な暗号化手法を反映していない可能性があり、十分に検討されていない可能性があります。コピーアンドペーストのセキュリティなどはありません。回答で、少なくとも文字列変換を考慮に入れる必要があります。2Dバーコードが含まれている実際の質問は非常に広範であり、顧客固有のソリューションが必要です。
Maarten Bodewes

回答:


156

これは、Googleを介して表示される最初のページであり、すべての実装のセキュリティの脆弱性に悩まされているため、元の投稿から7年経過しているため、他のユーザーの暗号化に関する情報を追加するためにこれを投稿しています。私はコンピューターエンジニアリングの修士号を取得し、暗号の勉強と学習に多くの時間を費やしたので、インターネットをより安全な場所にするために2セントを投入しています。

また、特定の状況では多くの実装が安全である可能性がありますが、なぜそれらを使用して誤って間違える可能性があることに注意してください。特別な理由がない限り、入手可能な最も強力なツールを使用してください。全体的に、ライブラリを使用し、できれば細かい詳細から離れることを強くお勧めします。

アップデート4/5/18:理解しやすくするためにいくつかの部分を書き直し、推奨ライブラリをJasyptからGoogleの新しいライブラリTink変更しました。既存の設定からJasyptを完全に削除することをお勧めします。

序文

安全な対称暗号化の基本を以下に概説し、人々が標準のJavaライブラリを使用して独自に暗号化を実装するときにオンラインで見られる一般的な間違いを指摘します。すべての詳細をスキップしたい場合は、Googleの新しいライブラリに移動します。Tinkをプロジェクトにインポートし、すべての暗号化にAES-GCMモ​​ードを使用すれば、安全です。

今あなたがJavaで暗号化する方法の要点を学びたいなら:)

ブロック暗号

まず最初に、対称鍵のブロック暗号を選択する必要があります。ブロック暗号は、疑似ランダムネスを作成するために使用されるコンピュータ機能/プログラムです。擬似ランダム性は、量子コンピュータ以外のコンピュータがそれと実際のランダム性の違いを識別できない偽のランダム性です。ブロック暗号は暗号化の基本要素のようなものであり、さまざまなモードやスキームで使用すると暗号化を作成できます。

さて、今日ブロック暗号アルゴリズムが利用可能については、確認してくださいNEVER、私は繰り返す決して使用しないDESを、私も絶対に使用しないでくださいと言うでしょう3DESを。SnowdenのNSAリリースでさえ、可能な限り疑似ランダムに近いことを確認できた唯一のブロック暗号はAES 256です。AES 128も存在します。違いは、AES 256が256ビットブロックで機能するのに対し、AES 128は128ブロックで機能することです。全体として、AES 128は安全であると見なされていますが、いくつかの弱点が発見されていますが、256はそれと同じくらい堅固です。

おもしろい事実DESは、NSAが最初に設立されたときにNSAによって破壊され、実際には数年間秘密にされていました。3DESは安全であるとまだ主張している人もいますが、3DESの弱点を見つけて分析した研究論文はかなりあります。

暗号化モード

暗号化は、ブロック暗号を取り、特定のスキームを使用するときに作成されます。これにより、ランダム性がキーと組み合わされ、キーを知っている限りリバーシブルなものが作成されます。これは暗号化モードと呼ばれます。

暗号化モードの例と、何が起こっているのかを視覚的に理解できるようにECBとして知られる最も単純なモードを次に示します。

ECBモード

最も一般的にオンラインで表示される暗号化モードは次のとおりです。

ECB CTR、CBC、GCM

リストされたモード以外にも他のモードが存在し、研究者は既存の問題を改善するために常に新しいモードに向けて取り組んでいます。

次に、実装と安全なものに移りましょう。ECBは絶対に使用しないでください。これは、有名なLinuxペンギンに示されているように、繰り返しデータを非表示にするのに適していません。Linuxペンギンの例

Javaで実装する場合、次のコードを使用すると、デフォルトでECBモードが設定されることに注意してください。

Cipher cipher = Cipher.getInstance("AES");

...危険これは脆弱性です!残念ながら、これはStackOverflow全体とオンラインでチュートリアルと例で見られます。

ナンスとIV

ECBモードで見つかった問題に対応して、IVとも呼ばれるアナウンスが作成されました。新しいランダム変数を生成し、それをすべての暗号化に添付することで、同じ2つのメッセージを暗号化すると、異なるメッセージが生成されるという考え方です。この背後にある美しさは、IVまたはnonceが公の知識であることです。これは、攻撃者がこれにアクセスできることを意味しますが、あなたの鍵がない限り、攻撃者はその知識で何もできません。

私が目にする一般的な問題は、人々がコード内の同じ固定値と同じようにIVを静的な値として設定することです。そして、IVを繰り返すときに、実際に暗号化のセキュリティ全体を危険にさらすときのIVの落とし穴があります。

ランダムIVの生成

SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

注: SHA1は壊れていますが、SHA256をこのユースケースに適切に実装する方法を見つけることができなかったため、誰かがこれをクラックして更新したい場合は、すばらしいでしょう!また、SHA1攻撃は従来とは異なり、巨大なクラスターでクラックが発生するまで数年かかる場合があります。詳細はこちら。

CTRの実装

CTRモードではパディングは必要ありません。

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

CBCの実装

CBCモードを実装する場合は、次のようにPKCS7Paddingを使用して実装します。

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

CBCおよびCTRの脆弱性とGCMを使用する理由

CBCやCTRなどの他のいくつかのモードは安全ですが、攻撃者が暗号化されたデータを反転させ、解読時にその値を変更するという問題に遭遇します。たとえば、架空の銀行のメッセージ「Sell 100」を暗号化するとします。暗号化されたメッセージは次のようになります。

これを回避するために、インターネットの大部分はGCMを使用しており、HTTPSが表示されるたびに、おそらくGCMを使用しています。GCMは暗号化されたメッセージにハッシュで署名し、この署名を使用してメッセージが変更されていないことを確認します。

GCMは複雑であるため、実装は避けます。Googleの新しいライブラリTinkを使用する方がよいでしょう。ここでも、誤ってIVを繰り返してしまうと、GCMの場合のキーが危険にさらされるため、これが究極のセキュリティ欠陥です。新しい研究者は、IVを繰り返しても暗号化モードに向けて取り組んでいます。IVを繰り返しても、キーは危険ではありませんが、これはまだ主流ではありません。

GCMを実装する場合は、ここにGCM実装へのリンクがあります。ただし、セキュリティを確保することはできず、適切に実装されているかどうかはわかりませんが、根拠が下がっています。また、GCMではパディングがないことに注意してください。

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

キーとパスワード

もう1つの非常に重要なメモは、暗号化に関して、キーとパスワードは同じものではないということです。暗号化のキーは、安全であると見なされるために、ある程度のエントロピーとランダム性を持っている必要があります。このため、適切な暗号化ライブラリを使用してキーを生成する必要があります。

したがって、ここで実行できる実装は2つあります。最初の実装は、このStackOverflowスレッドにあるコードを使用してランダムキーを生成することです。このソリューションでは、安全な乱数ジェネレータを使用して、使用できる鍵を最初から作成します。

安全性の低いもう1つのオプションは、パスワードなどのユーザー入力を使用することです。前述の問題は、パスワードに十分なエントロピーがないため、パスワードを取得して強化するアルゴリズムであるPBKDF2を使用する必要があることです。これが私が気に入っStackOverflowの実装です。ただし、Google Tinkライブラリにはこれらすべてが組み込まれており、利用する必要があります。

Androidデベロッパー

ここで指摘すべき重要な点の1つは、Androidコードはリバースエンジニアリングが可能であり、ほとんどの場合、ほとんどのJavaコードもそうであるということです。つまり、コードにパスワードをプレーンテキストで格納する場合です。ハッカーは簡単にそれを取得できます。通常、これらのタイプの暗号化では、非対称暗号化などを使用します。これはこの投稿の範囲外であるため、ここでは詳しく説明しません。

2013年興味深い記事:Androidでの暗号化の実装の88%が不適切に行われたことを指摘しています。

最終的な考え

ここでも、cryptoのJavaライブラリを直接実装せずにGoogle Tinkを使用することをお勧めします。これにより、すべてのアルゴリズムを適切に実装するという優れた仕事をしているので、頭痛の種を省くことができます。そして、それでも、Tink githubで出された問題、あちこちに存在する脆弱性のポップアップを確認するようにしてください。

質問やフィードバックがあれば、遠慮なくコメントしてください!セキュリティは常に変化しており、それに追いつくために最善を尽くす必要があります:)


15
これは私が今まで見た中で最もきれいなものです。
Seraf

1
@SabirKhan懸念の原因になる可能性がありますが、コアアルゴリズムはまだ壊れていないため、あまり心配する必要はありません。信頼できない場合はgithub.com/google/keyczarもチェックしてください。これはgooglesセキュリティチームによって開発されました。
Konstantino Sparakis 2017年

1
@KonstantinoSparakis:JasyptのBasicTextEncryptorおよびStrongTextEncryptorのドキュメントを誤って解釈しなかった場合、これらのクラスは暗号化にDESおよび3DESを使用します。これは、読者が使用しないように指示するものです。IMOでは、与えられたコード例をJasyptのStandardPBEStringEncryptorを使用し、使用するAESアルゴリズムを手動で定義するコード例に置き換える必要があります。
xpages-noob 2018

1
@ xpages-noob投稿を更新しました。私は実際に暗号化用にサポートされている最新のライブラリであるGoogle Tinkを見つけたので、チェックする必要があります!
Konstantino Sparakis

2
AESブロックサイズは128ビットです。AES 256では、キーサイズは256ビットです。同様に、AES 192とAES128。また、Java 8以降、SHA1PRNG よりもgetInstanceStrong()メソッドの方Cipherが好ましい
Saptarshi Basu

110

DES3DESAESなどの広く利用可能な標準の対称暗号を使用することをお勧めします。これは最も安全なアルゴリズムではありませんが、多くの実装があり、バーコードの情報を解読することになっている人にキーを与えるだけで済みます。javax.crypto.Cipherは、ここで操作したいものです。

暗号化するバイトが入っているとしましょう

byte[] input;

次に、キーと初期化ベクトルバイトが必要になります

byte[] keyBytes;
byte[] ivBytes;

これで、選択したアルゴリズムの暗号を初期化できます。

// wrap key data in Key/IV specs to pass to cipher
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

暗号化は次のようになります。

cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted= new byte[cipher.getOutputSize(input.length)];
int enc_len = cipher.update(input, 0, input.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);

そして、このような復号化:

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(encrypted, 0, enc_len, decrypted, 0);
dec_len += cipher.doFinal(decrypted, dec_len);

9
この例を更新してDESedeアルゴリズムを参照することを提案できますか?これは一般的な質問(および回答)であるため、今日の標準では暗号が非常に弱いため、人々にDESの使用を奨励するのは残念です。
Duncan Jones、

javax.crypto.BadPaddingExceptionに何か問題があります:指定された最終ブロックが記述中に適切にパディングされない
好奇心

2
@Duncan Indeed DESは弱いですが、DESede(別名TipleDES)よりもAESの方が好ましいと思います:http
//security.stackexchange.com/a/26181/69785

2
これはAES / GCM / NoPaddingになるように更新する必要があります。DESはブルートフォース攻撃に対して脆弱です。TripleDesも推奨されません
Konstantino Sparakis

1
以下のKonstantino Sparakisからの回答は、これよりもはるかに優れています。
スティーブ

22

警告

これをある種のセキュリティ測定として使用しないでください。

この投稿の暗号化メカニズムはワンタイムパッドであるため、攻撃者は2つの暗号化されたメッセージを使用して秘密鍵を簡単に復元できます。XOR 2暗号化メッセージとキーを取得します。とても簡単です!

ムサが指摘


私は、SunのJREにあるSunのBase64Encoder / Decoderを使用して、lib内のさらに別のJARを回避しています。これは、OpenJDKまたは他のJREを使用するという観点からは危険です。それ以外に、Apache commons libをEncoder / Decoderで使用することを検討する必要がある別の理由はありますか?

public class EncryptUtils {
    public static final String DEFAULT_ENCODING = "UTF-8"; 
    static BASE64Encoder enc = new BASE64Encoder();
    static BASE64Decoder dec = new BASE64Decoder();

    public static String base64encode(String text) {
        try {
            return enc.encode(text.getBytes(DEFAULT_ENCODING));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }//base64encode

    public static String base64decode(String text) {
        try {
            return new String(dec.decodeBuffer(text), DEFAULT_ENCODING);
        } catch (IOException e) {
            return null;
        }
    }//base64decode

    public static void main(String[] args) {
        String txt = "some text to be encrypted";
        String key = "key phrase used for XOR-ing";
        System.out.println(txt + " XOR-ed to: " + (txt = xorMessage(txt, key)));

        String encoded = base64encode(txt);       
        System.out.println(" is encoded to: " + encoded + " and that is decoding to: " + (txt = base64decode(encoded)));
        System.out.print("XOR-ing back to original: " + xorMessage(txt, key));
    }

    public static String xorMessage(String message, String key) {
        try {
            if (message == null || key == null) return null;

            char[] keys = key.toCharArray();
            char[] mesg = message.toCharArray();

            int ml = mesg.length;
            int kl = keys.length;
            char[] newmsg = new char[ml];

            for (int i = 0; i < ml; i++) {
                newmsg[i] = (char)(mesg[i] ^ keys[i % kl]);
            }//for i

            return new String(newmsg);
        } catch (Exception e) {
            return null;
        }
    }//xorMessage
}//class

1
また、sun.misc.BASE64Encoderを介してこのソリューションの提案を使用しましたが、エンコードにかなり大きな文字列を使用すると、エンコーダーはチャンクされた文字列(それぞれ76文字)を返しました。次に、チャンク化されていないエンコード方式を提供するApache Commons Codec Base64に切り替えました!
basZero

78
説明した暗号化メカニズムは、2回以上使用すると非常に危険です。それがワンタイムパッドと呼ばれる理由です。秘密鍵は、攻撃者が2つの暗号化されたメッセージを使用して簡単に復元できます。xor 2暗号化メッセージとキーを取得します。とても簡単です!
xtrem

3
そのアイデアは重いものではなく、単にPDF-417 2Dバーコードで書かれたものを読み取ろうとする人々を跳ね返すためのものです。とにかく、誰にとっても重要ではないいくつかのインデックスしかありません...
ante.sabo

2
OK。誰かがこれを暗号化メカニズムとして使用することを心配しました。
xtrem

暗号化では、エンコーダー(例:BASE64Encoder)を使用して総当たり攻撃を回避できます。
Jagrut Dalwadi 2016年

13

おかげで、あなたのコードを使用してこのクラスを作成した可能性があります

オブジェクトクリプター

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

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


public class ObjectCrypter {

private Cipher deCipher;
private Cipher enCipher;
private SecretKeySpec key;
private IvParameterSpec ivSpec;


public ObjectCrypter(byte[] keyBytes,   byte[] ivBytes) {
    // wrap key data in Key/IV specs to pass to cipher


     ivSpec = new IvParameterSpec(ivBytes);
    // create the cipher with the algorithm you choose
    // see javadoc for Cipher class for more info, e.g.
    try {
         DESKeySpec dkey = new  DESKeySpec(keyBytes);
          key = new SecretKeySpec(dkey.getKey(), "DES");
         deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
         enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
public byte[] encrypt(Object obj) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, ShortBufferException, BadPaddingException {
    byte[] input = convertToByteArray(obj);
    enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    return enCipher.doFinal(input);




//  cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
//  byte[] encypted = new byte[cipher.getOutputSize(input.length)];
//  int enc_len = cipher.update(input, 0, input.length, encypted, 0);
//  enc_len += cipher.doFinal(encypted, enc_len);
//  return encypted;


}
public Object decrypt( byte[]  encrypted) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException {
    deCipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

    return convertFromByteArray(deCipher.doFinal(encrypted));

}



private Object convertFromByteArray(byte[] byteObject) throws IOException,
        ClassNotFoundException {
    ByteArrayInputStream bais;

    ObjectInputStream in;
    bais = new ByteArrayInputStream(byteObject);
    in = new ObjectInputStream(bais);
    Object o = in.readObject();
    in.close();
    return o;

}



private byte[] convertToByteArray(Object complexObject) throws IOException {
    ByteArrayOutputStream baos;

    ObjectOutputStream out;

    baos = new ByteArrayOutputStream();

    out = new ObjectOutputStream(baos);

    out.writeObject(complexObject);

    out.close();

    return baos.toByteArray();

}


}

ここに関連する質問を投稿しました!
user2023507 2014

暗号化と復号化中にdifferentKeysを渡してもテキストが返されないのではないでしょうか?それはここでは起こっていないようです。PS:このテストを実行するために、このクラスのさまざまなオブジェクトを使用しています。
instanceOfObject

6

2019年12月12日更新

CBCのような他のいくつかのモードとは異なり、GCMモードではIVが予測不能である必要はありません。唯一の要件は、IVが特定のキーでの呼び出しごとに一意である必要があることです。与えられたキーに対して1回繰り返すと、セキュリティが侵害される可能性があります。これを実現する簡単な方法は、次に示すように、強力な疑似乱数ジェネレータからのランダムIVを使用することです。

シーケンスまたはタイムスタンプをIVとして使用することも可能ですが、聞こえるほど簡単ではない場合があります。たとえば、システムが永続ストアですでにIVとして使用されているシーケンスを正しく追跡しない場合、呼び出しはシステムの再起動後にIVを繰り返す可能性があります。同様に、完璧な時計はありません。コンピューターの時計などの調整

また、キーは2 ^ 32回の呼び出しごとにローテーションする必要があります。IV要件の詳細については、この回答NISTの推奨事項を参照してください。


これは、Java 8で次の点を考慮して書いたばかりの暗号化および復号化コードです。誰かがこれが役立つことを願っています:

  1. 暗号化アルゴリズム:256ビットのキーを持つブロック暗号AESは十分に安全であると考えられています。メッセージ全体を暗号化するには、モードを選択する必要があります。認証された暗号化(機密性と整合性の両方を提供)をお勧めします。GCM、CCM、およびEAXは、最も一般的に使用される認証済み暗号化モードです。通常、GCMが推奨され、GCM専用の命令を提供するIntelアーキテクチャで適切に機能します。これら3つのモードはすべてCTRベース(カウンターベース)モードであるため、パディングは必要ありません。結果として、それらはパディング関連の攻撃に対して脆弱ではありません

  2. GCMには初期化ベクトル(IV)が必要です。IVは秘密ではありません。唯一の要件は、ランダムであるか予測できないことです。Javaでは、このSecuredRandomクラスは暗号学的に強力な疑似乱数を生成するためのものです。getInstance()メソッドには、疑似乱数生成アルゴリズムを指定できます。ただし、Java 8以降、推奨される方法は、getInstanceStrong()によって構成および提供される最強のアルゴリズムを使用するメソッドを使用することです。Provider

  3. NISTは、GCMに96ビットIVを推奨し、設計の相互運用性、効率、およびシンプルさを促進します

  4. 追加のセキュリティを確保するために、次の実装でSecureRandomは、疑似ランダムバイト生成の2 ^ 16バイトごとの生成後に再シードされます

  5. 受信者は、暗号文を復号化できるようにIVを知っている必要があります。したがって、IVは暗号テキストとともに転送する必要があります。一部の実装では、IVをAD(関連データ)として送信します。これは、認証タグが暗号テキストとIVの両方で計算されることを意味します。ただし、それは必須ではありません。意図的な攻撃またはネットワーク/ファイルシステムエラーが原因でIVが送信中に変更された場合でも、認証タグの検証は失敗します。

  6. 文字列は不変であり、使用後にそれらを消去できないため、文字列を使用してクリアテキストメッセージまたはキーを保持することはできません。これらのクリアされていない文字列はメモリに残り、ヒープダンプに表示される場合があります。同じ理由で、これらの暗号化または復号化メソッドを呼び出すクライアントは、メッセージまたはキーが不要になった後、それらを保持するすべての変数または配列をクリアする必要があります。

  7. 一般的な推奨事項に従って、コードにハードコードされているプロバイダーはありません

  8. 最後に、ネットワークまたはストレージを介した送信では、鍵または暗号テキストをBase64エンコードを使用してエンコードする必要があります。Base64の詳細については、こちらをご覧ください。Java 8アプローチに従う必要があります

バイト配列は以下を使用してクリアできます。

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

ただし、Java 8のように、クリアする簡単な方法はありませんSecretKeyspecし、SecretKeyこれらの二つのインタフェースの実装の方法実装されているようには見えませんdestroy()インターフェースのをDestroyable。次のコードでは、をクリアしてリフレクションSecretKeySpecSecretKey使用する別のメソッドが記述されています。

キーは、以下で説明する2つの方法のいずれかを使用して生成する必要があります。

キーはパスワードのような秘密ですが、人間が使用することを意図したパスワードとは異なり、キーは暗号化アルゴリズムで使用することを意図しているため、上記の方法のみを使用して生成する必要があります。

package com.sapbasu.javastudy;

import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

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

public class Crypto {

  private static final int AUTH_TAG_SIZE = 128; // bits

  // NIST recommendation: "For IVs, it is recommended that implementations
  // restrict support to the length of 96 bits, to
  // promote interoperability, efficiency, and simplicity of design."
  private static final int IV_LEN = 12; // bytes

  // number of random number bytes generated before re-seeding
  private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16);

  private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";

  private static final List<Integer> ALLOWED_KEY_SIZES = Arrays
      .asList(new Integer[] {128, 192, 256}); // bits

  private static SecureRandom prng;

  // Used to keep track of random number bytes generated by PRNG
  // (for the purpose of re-seeding)
  private static int bytesGenerated = 0;

  public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception {

    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Length of message cannot be 0");
    }

    if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) {
      throw new IllegalArgumentException("Size of key must be 128, 192 or 256");
    }

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

    byte[] iv = getIV(IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec);
    byte[] messageCipher = cipher.doFinal(input);

    // Prepend the IV with the message cipher
    byte[] cipherText = new byte[messageCipher.length + IV_LEN];
    System.arraycopy(iv, 0, cipherText, 0, IV_LEN);
    System.arraycopy(messageCipher, 0, cipherText, IV_LEN,
        messageCipher.length);
    return cipherText;
  }

  public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception {
    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Input array cannot be empty");
    }

    byte[] iv = new byte[IV_LEN];
    System.arraycopy(input, 0, iv, 0, IV_LEN);

    byte[] messageCipher = new byte[input.length - IV_LEN];
    System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec);

    return cipher.doFinal(messageCipher);
  }

  public byte[] getIV(int bytesNum) {

    if (bytesNum < 1) throw new IllegalArgumentException(
        "Number of bytes must be greater than 0");

    byte[] iv = new byte[bytesNum];

    prng = Optional.ofNullable(prng).orElseGet(() -> {
      try {
        prng = SecureRandom.getInstanceStrong();
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Wrong algorithm name", e);
      }
      return prng;
    });

    if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) {
      prng.setSeed(prng.generateSeed(bytesNum));
      bytesGenerated = 0;
    }

    prng.nextBytes(iv);
    bytesGenerated = bytesGenerated + bytesNum;

    return iv;
  }

  private static void clearSecret(Destroyable key)
      throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    Field keyField = key.getClass().getDeclaredField("key");
    keyField.setAccessible(true);
    byte[] encodedKey = (byte[]) keyField.get(key);
    Arrays.fill(encodedKey, Byte.MIN_VALUE);
  }
}

暗号化キーは、主に次の2つの方法で生成できます。

  • パスワードなし

    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
  • パスワードあり

    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[32];
    random.nextBytes(salt);
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, 
       keyLength);
    SecretKeyFactory keyFactory = 
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);

コメントに基づいて更新

@MaartenBodewesによって指摘されたように、私の答えはString質問で必要とされるものを処理しませんでした。したがって、誰かがこの回答につまずいて処理に疑問を感じた場合に備えて、そのギャップを埋めようとしますString

回答の前半で示したように、aで機密情報を処理するStringことStringは、不変であるため、一般的にはお勧めできません。そして、私たちが知っStringているように、aに強い参照がない場合でも、ガベージコレクターはすぐに急いでヒープから削除しません。したがって、Stringプログラムにアクセスできない場合でも、は不明な時間枠でメモリ内に存在し続けます。その問題は、その時間枠のヒープダンプが機密情報を明らかにすることです。したがって、すべての機密情報をバイト配列または文字配列で処理し、目的が果たされたら配列に0を入力することをお勧めします。

ただし、これらすべての知識があってもString、暗号化される機密情報がにある場合は、まずそれをバイト配列に変換し、上記で紹介したencryptおよびdecrypt関数を呼び出す必要があります。(他の入力キーは、上記のコードスニペットを使用して生成できます)。

A Stringは次の方法でバイトに変換できます。

byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);

Java 8の時点でStringは、内部的にUTF-16エンコードされたヒープに格納されています。ただし、特にASCII文字のUTF-8場合UTF-16、通常はに比べてスペースが少ないため、ここでは使用しました。

同様に、暗号化されたバイト配列も次のように文字列に変換できます。

String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);

1
現在の暗号化プラクティスに準拠しているように見えるので、この回答に賛成したいのと同じくらい、文字列の処理はまったく見られず、GCMモードの使用方法の説明にすぎません。そのため、質問答えることができません。
Maarten Bodewes

1
@MaartenBodewesフィードバックを確認し、共有するために時間を割いていただきありがとうございます。String上で作成した関数を使用してを暗号化するのは簡単だと理解して、私はこれを書きました。しかし、あなたのコメントを読んだ後の再調査では、それが明白でないかもしれないことを理解しています。きっと編集して詳細を追加していきます。
Saptarshi Basu

5

これはどう:

private static byte[] xor(final byte[] input, final byte[] secret) {
    final byte[] output = new byte[input.length];
    if (secret.length == 0) {
        throw new IllegalArgumentException("empty security key");
    }
    int spos = 0;
    for (int pos = 0; pos < input.length; ++pos) {
        output[pos] = (byte) (input[pos] ^ secret[spos]);
        ++spos;
        if (spos >= secret.length) {
            spos = 0;
        }
    }
    return output;
}

私には問題なく機能し、かなりコンパクトです。


エントリパラメータsecret == nullまたはinput == nullの場合はどうなりますか?文字列ではなく、その後のバイトでの作業はOKですが、私の場合は無関係だった。..重要なものだけだが、これが可能コードする任意の文字で、任意のデバイスで読めると復号化可能でなければならないということです...
ante.sabo

@ ante.saboは明らかに、NPEをスローします。これは、NULLを処理する唯一の方法です。
Miha_x64 2017年

input.length <= secret.length保持され、secret再利用されない限り、これは安全であり、one-time-pad。例ではinput.length > secret.length、このの変種であるヴィジュネル暗号と非常に弱いと考えました。
トリヒナー

5

Jasyptを使用できます

Jasyptを使用すると、パスワードの暗号化とチェックは次のように簡単です...

StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);

暗号化:

String myEncryptedText = textEncryptor.encrypt(myText);

解読:

String plainText = textEncryptor.decrypt(myEncryptedText);

Gradle:

compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'

特徴:

Jasyptは、簡単な単方向(ダイジェスト)および双方向の暗号化技術を提供します。

デフォルトのJava VMだけでなく、任意のJCEプロバイダーで使用できるオープンAPI。Jasyptは、Bouncy Castleなどの有名なプロバイダーで簡単に使用できます。もっと詳しく知る。

ユーザーのパスワードのより高いセキュリティ。もっと詳しく知る。

バイナリ暗号化のサポート。Jasyptはバイナリ(バイト配列)のダイジェストと暗号化を可能にします。必要に応じてオブジェクトやファイルを暗号化します(たとえば、ネット経由で送信するため)。

番号暗号化のサポート。テキストとバイナリに加えて、数値のダイジェストと暗号化を可能にします(BigIntegerとBigDecimal、Hibernate永続化のための暗号化では他の数値型がサポートされます)。もっと詳しく知る。

完全にスレッドセーフです。

マルチプロセッサ/マルチコアシステムで高性能を達成するための暗号化/ダイジェスタープーリングのサポート。

ライブラリの軽量(「ライト」)バージョンが含まれているため、モバイルプラットフォームなどのサイズ制限のある環境での管理性が向上します。

暗号化を初めて使用するユーザー向けに、設定不要の簡単な暗号化ツールと、パワーユーザー向けに高度に設定可能な標準暗号化ツールの両方を提供します。

暗号化された方法でマップされたエンティティのフィールドを保持するためのHibernate 3および4のオプションの統合。フィールドの暗号化はHibernateマッピングファイルで定義されており、アプリケーションの残りの部分では透過性を維持します(機密の個人データ、多くの読み取りが有効なユーザーがいるデータベースで役立ちます...)。テキスト、バイナリ、数値、ブール値、日付を暗号化します...詳細。

Spring 2、Spring 3.0、およびSpring 3.1に固有の統合機能を備えたSpringアプリケーションにシームレスに統合できます。jasyptのすべてのダイジェスターとエンクリプターは、Springから簡単に使用できるように設計されています(インスタンス化され、依存性が注入されています...)。また、スレッドセーフであるため、Springのようなシングルトン指向の環境で同期の心配なしに使用できます。詳細:Spring 2、Spring 3.0、Spring 3.1。

Spring Security(旧Acegi Security)のオプションの統合により、セキュリティフレームワークのパスワード暗号化と照合タスクを実行し、より安全なパスワード暗号化メカニズムを使用してユーザーのパスワードのセキュリティを向上させ、高度な構成と制御を提供します。もっと詳しく知る。

データベースのパスワードなどの機密情報を含む、アプリケーションの構成ファイルのすべてまたは一部を暗号化するための高度な機能を提供します。暗号化された構成をプレーンなSpringベースのアプリケーションやHibernate対応のアプリケーションにシームレスに統合します。もっと詳しく知る。

使いやすいCLI(コマンドラインインターフェイス)ツールを提供して、開発者が暗号化されたデータを初期化し、暗号化/復号化/ダイジェスト操作をメンテナンスタスクまたはスクリプトに含めることができるようにします。もっと詳しく知る。

安全なアプリケーションでURLをより強力に暗号化するために、Apache Wicketに統合します。

開発者がデータに対して実際に行っていることをよりよく理解できるようにする、包括的なガイドとjavadocドキュメント。

堅牢な文字セットのサポート。元の文字セットがどれであっても、テキストを適切に暗号化およびダイジェストするように設計されています。日本語、韓国語、アラビア語などの言語を完全にサポートします。エンコーディングやプラットフォームの問題はありません。

非常に高いレベルの構成機能:開発者は、暗号化に使用するパスワードをリモートHTTPSサーバーなどに要求するように「暗号化プログラム」に指示するなどのトリックを実装できます。それはあなたのセキュリティニーズを満たすことができます。


1
しかし、どのようなセキュリティがJasypt提供されますか?私は彼らのウェブサイトからそれを理解することはできません。選択された平文攻撃の下でそれは区別できませんか?誠実さ?守秘義務?
トリヒナー2018

4

以下は、Spring Singletonとしてのmeta64.comからの私の実装です。機能する各呼び出しに対してciperインスタンスを作成する場合、「同期」された呼び出しを削除できますが、「暗号」はスレッドセーフではないことに注意してください。

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

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

@Component
@Scope("singleton")
public class Encryptor {

    @Value("${aeskey}")
    private String keyStr;

    private Key aesKey = null;
    private Cipher cipher = null;

    synchronized private void init() throws Exception {
        if (keyStr == null || keyStr.length() != 16) {
            throw new Exception("bad aes key configured");
        }
        if (aesKey == null) {
            aesKey = new SecretKeySpec(keyStr.getBytes(), "AES");
            cipher = Cipher.getInstance("AES");
        }
    }

    synchronized public String encrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        return toHexString(cipher.doFinal(text.getBytes()));
    }

    synchronized public String decrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.DECRYPT_MODE, aesKey);
        return new String(cipher.doFinal(toByteArray(text)));
    }

    public static String toHexString(byte[] array) {
        return DatatypeConverter.printHexBinary(array);
    }

    public static byte[] toByteArray(String s) {
        return DatatypeConverter.parseHexBinary(s);
    }

    /*
     * DO NOT DELETE
     * 
     * Use this commented code if you don't like using DatatypeConverter dependency
     */
    // public static String toHexStringOld(byte[] bytes) {
    // StringBuilder sb = new StringBuilder();
    // for (byte b : bytes) {
    // sb.append(String.format("%02X", b));
    // }
    // return sb.toString();
    // }
    //
    // public static byte[] toByteArrayOld(String s) {
    // int len = s.length();
    // byte[] data = new byte[len / 2];
    // for (int i = 0; i < len; i += 2) {
    // data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i +
    // 1), 16));
    // }
    // return data;
    // }
}

3
これは恐ろしいECBモードで暗号化します。少なくともCBCモードまたはGCMモードを設定する必要があります
Konstantino Sparakis

Konstantintoという提案をありがとう、私はそれをグーグル検索し、単に「AES」の代わりに「AES / CBC / PKCS5Padding」をCipherのInit文字列として使用するコードを見つけましたが、さらに詳しく調べます。または、実際の修正を提供して、他の人がより良い方法を理解できるようにすることもできます。ただし、CBCの詳細は別として、私のソリューションは最も単純で安全であり、他のソリューションよりも優れていることに値します。

心配はいりません。暗号は複雑なテーマです。悲しいことに、このページのすべての実装は壊れており、悲しいことに、Googleを使用して「Java暗号化の方法」を検索するときにポップアップする最初のページです。機会があれば、すべて修正しようと思います。
Konstantino Sparakis

私の例はこれと同じです: docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/… 必要な場合を除いてCipher.getInstance( "AES / ECB / PKCS5Padding"); 私のコードは、完全に16バイト長の暗号化キーを持ついくつかのプロパティファイルがあることを前提としていますが、「ユーザー指定」のパスワードからの文字列を暗号化するために、oracleページ(上記にリンク)はその方法も示しています。

1
したがって、ECBの問題は、周波数分析に対して非常に脆弱であることです。Linuxペンギンの有名な例があります。blog.filippo.io/ the-ecb-penguinを参照してください。画像が暗号化されているにもかかわらず、ペンギンであることがわかります。私は先に進み、以下のテーマについて私の考えを書きました:) stackoverflow.com/a/43779197/2607972
Konstantino Sparakis 2017年

4

ここでは、機密性完全性を提供する、バイトの暗号化のみjava.*javax.crypto.*依存するシンプルなソリューションを示します。これは、キロバイトのオーダーの短いメッセージに対する選択された平文攻撃では区別できません

パディングなしAESGCMモードで使用します。128ビットのキーはPBKDF2、多くの繰り返しと提供されたパスワードからの静的ソルトによって導出されます。これにより、ブルートフォースパスワードの実行が困難になり、キー全体にエントロピーが分散されます。

ランダムな初期化ベクトル(IV)が生成され、暗号文の先頭に追加されます。さらに、静的バイト0x01は「バージョン」として最初のバイトとして付加されます。

メッセージ全体は、以下によって生成されるメッセージ認証コード(MAC)に入ります。 AES/GCM

ここで、機密性完全性を提供するゼロ外部依存暗号化クラス:

package ch.n1b.tcrypt.utils;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * This class implements AES-GCM symmetric key encryption with a PBKDF2 derived password.
 * It provides confidentiality and integrity of the plaintext.
 *
 * @author Thomas Richner
 * @created 2018-12-07
 */
public class AesGcmCryptor {

    // /crypto/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode
    private static final byte VERSION_BYTE = 0x01;
    private static final int VERSION_BYTE_LENGTH = 1;
    private static final int AES_KEY_BITS_LENGTH = 128;


    // fixed AES-GCM constants
    private static final String GCM_CRYPTO_NAME = "AES/GCM/NoPadding";
    private static final int GCM_IV_BYTES_LENGTH = 12;
    private static final int GCM_TAG_BYTES_LENGTH = 16;

    // can be tweaked, more iterations = more compute intensive to brute-force password
    private static final int PBKDF2_ITERATIONS = 1024;

    // protects against rainbow tables
    private static final byte[] PBKDF2_SALT = hexStringToByteArray("4d3fe0d71d2abd2828e7a3196ea450d4");

    public String encryptString(char[] password, String plaintext) throws CryptoException {

        byte[] encrypted = null;
        try {
            encrypted = encrypt(password, plaintext.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException //
                | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException //
                | InvalidKeySpecException e) {
            throw new CryptoException(e);
        }
        return byteArrayToHexString(encrypted);
    }

    public String decryptString(char[] password, String ciphertext)
            throws CryptoException {

        byte[] ct = hexStringToByteArray(ciphertext);
        byte[] plaintext = null;
        try {
            plaintext = decrypt(password, ct);
        } catch (AEADBadTagException e) {
            throw new CryptoException(e);
        } catch ( //
                NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException //
                        | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException //
                        | BadPaddingException e) {
            throw new CryptoException(e);
        }
        return new String(plaintext, StandardCharsets.UTF_8);
    }

    /**
     * Decrypts an AES-GCM encrypted ciphertext and is
     * the reverse operation of {@link AesGcmCryptor#encrypt(char[], byte[])}
     *
     * @param password   passphrase for decryption
     * @param ciphertext encrypted bytes
     * @return plaintext bytes
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws IllegalArgumentException           if the length or format of the ciphertext is bad
     * @throws CryptoException
     */
    public byte[] decrypt(char[] password, byte[] ciphertext)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        // input validation
        if (ciphertext == null) {
            throw new IllegalArgumentException("ciphertext cannot be null");
        }

        if (ciphertext.length <= VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH) {
            throw new IllegalArgumentException("ciphertext too short");
        }

        // the version must match, we don't decrypt other versions
        if (ciphertext[0] != VERSION_BYTE) {
            throw new IllegalArgumentException("wrong version: " + ciphertext[0]);
        }

        // input seems legit, lets decrypt and check integrity

        // derive key from password
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);

        // init cipher
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec params = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8,
                ciphertext,
                VERSION_BYTE_LENGTH,
                GCM_IV_BYTES_LENGTH
        );
        cipher.init(Cipher.DECRYPT_MODE, key, params);

        final int ciphertextOffset = VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH;

        // add version and IV to MAC
        cipher.updateAAD(ciphertext, 0, ciphertextOffset);

        // decipher and check MAC
        return cipher.doFinal(ciphertext, ciphertextOffset, ciphertext.length - ciphertextOffset);
    }

    /**
     * Encrypts a plaintext with a password.
     * <p>
     * The encryption provides the following security properties:
     * Confidentiality + Integrity
     * <p>
     * This is achieved my using the AES-GCM AEAD blockmode with a randomized IV.
     * <p>
     * The tag is calculated over the version byte, the IV as well as the ciphertext.
     * <p>
     * Finally the encrypted bytes have the following structure:
     * <pre>
     *          +-------------------------------------------------------------------+
     *          |         |               |                             |           |
     *          | version | IV bytes      | ciphertext bytes            |    tag    |
     *          |         |               |                             |           |
     *          +-------------------------------------------------------------------+
     * Length:     1B        12B            len(plaintext) bytes            16B
     * </pre>
     * Note: There is no padding required for AES-GCM, but this also implies that
     * the exact plaintext length is revealed.
     *
     * @param password  password to use for encryption
     * @param plaintext plaintext to encrypt
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeySpecException
     */
    public byte[] encrypt(char[] password, byte[] plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
            InvalidKeySpecException {

        // initialise random and generate IV (initialisation vector)
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);
        final byte[] iv = new byte[GCM_IV_BYTES_LENGTH];
        SecureRandom random = SecureRandom.getInstanceStrong();
        random.nextBytes(iv);

        // encrypt
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);

        // add IV to MAC
        final byte[] versionBytes = new byte[]{VERSION_BYTE};
        cipher.updateAAD(versionBytes);
        cipher.updateAAD(iv);

        // encrypt and MAC plaintext
        byte[] ciphertext = cipher.doFinal(plaintext);

        // prepend VERSION and IV to ciphertext
        byte[] encrypted = new byte[1 + GCM_IV_BYTES_LENGTH + ciphertext.length];
        int pos = 0;
        System.arraycopy(versionBytes, 0, encrypted, 0, VERSION_BYTE_LENGTH);
        pos += VERSION_BYTE_LENGTH;
        System.arraycopy(iv, 0, encrypted, pos, iv.length);
        pos += iv.length;
        System.arraycopy(ciphertext, 0, encrypted, pos, ciphertext.length);

        return encrypted;
    }

    /**
     * We derive a fixed length AES key with uniform entropy from a provided
     * passphrase. This is done with PBKDF2/HMAC256 with a fixed count
     * of iterations and a provided salt.
     *
     * @param password passphrase to derive key from
     * @param salt     salt for PBKDF2 if possible use a per-key salt, alternatively
     *                 a random constant salt is better than no salt.
     * @param keyLen   number of key bits to output
     * @return a SecretKey for AES derived from a passphrase
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private SecretKey deriveAesKey(char[] password, byte[] salt, int keyLen)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        if (password == null || salt == null || keyLen <= 0) {
            throw new IllegalArgumentException();
        }
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, keyLen);
        SecretKey pbeKey = factory.generateSecret(spec);

        return new SecretKeySpec(pbeKey.getEncoded(), "AES");
    }

    /**
     * Helper to convert hex strings to bytes.
     * <p>
     * May be used to read bytes from constants.
     */
    private static byte[] hexStringToByteArray(String s) {

        if (s == null) {
            throw new IllegalArgumentException("Provided `null` string.");
        }

        int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Invalid length: " + len);
        }

        byte[] data = new byte[len / 2];
        for (int i = 0; i < len - 1; i += 2) {
            byte b = (byte) toHexDigit(s, i);
            b <<= 4;
            b |= toHexDigit(s, i + 1);
            data[i / 2] = b;
        }
        return data;
    }

    private static int toHexDigit(String s, int pos) {
        int d = Character.digit(s.charAt(pos), 16);
        if (d < 0) {
            throw new IllegalArgumentException("Cannot parse hex digit: " + s + " at " + pos);
        }
        return d;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    public class CryptoException extends Exception {

        public CryptoException(Throwable cause) {
            super(cause);
        }
    }
}

ここに素敵なCLIを備えたプロジェクト全体:https : //github.com/trichner/tcrypt

編集:適切なencryptStringdecryptString


これが信じられない。ありがとうございました!私はコードから多くのことを学び、BadVersionException Exceptionクラスを作成した後、コードは初めて完全に機能しました。優れた!!
Morkus、2018年

私はこの試みが好きです。つまり、Saltは静的ではなくランダムである必要があります。反復もおそらく静的であってはなりません。GCMでは、タグの計算にすでにIVが含まれています。ただし、バージョン番号は含まれていません。移植性のためにプロバイダーを指定しないでください。「SunJCE」が、それをサポートするプラットフォームのデフォルトになります。このコードには、この特定の質問に必要なメッセージ文字列処理は含まれていません。
Maarten Bodewes

さてさて、私はもう少しそれをクリーンアップし、要求された追加encryptStringdecryptString:)
trichner

これは非常にうまくいきました。コードのty。このコードが適切に機能するには、API 19(Kit Kat)以上が必要です。
PGMacDesign

3

私はhttps://www.bouncycastle.org/のようなものを使用することを検討します。これは、多数の異なる暗号で好きなものを暗号化できるようにする事前構築済みのライブラリです。情報を保護したい場合、Base64を使用しても実際には保護されません。


1
暗号化されたランダムな暗号ライブラリを推奨するだけでは、質問に対する答えにはなりません。それ以外に、組み込みの暗号を使用してみませんか?
Maarten Bodewes

2

Javaがサポートするものを読むことができるいくつかのリンクがあります

データストリームの暗号化/復号化。

この例は、(AES、Blowfish、RC2、3DESなどの対称暗号化アルゴリズムを使用して)大量のデータを暗号化する方法を示しています。データは、暗号化メソッドEncryptBytes、EncryptString、EncryptBytesENC、またはEncryptStringENCのいずれかにチャンクで渡されます。(メソッド名は、入力のタイプ(文字列またはバイト配列)と戻り値のタイプ(エンコードされた文字列またはバイト配列)を示します。FirstChunkおよびLastChunkプロパティは、チャンクがストリームの最初、中間、または最後であるかどうかを示すために使用されます暗号化されます。デフォルトでは、FirstChunkとLastChunkの両方がtrueになり、渡されるデータは全体です。

JCERefGuide

Java暗号化の例


はい、Javaでサポートされている暗号化があります。ストリームの暗号化も要求されたものではありません。
Maarten Bodewes

2

多くの人がすでに言っているように、DESやAESのように過度に使用されている標準の暗号を使用する必要があります。

AESを使用してJavaで文字列を暗号化および復号化する方法の簡単な例。

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

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

public class EncryptorDemo {

    public static String encrypt(String key, String randomVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.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 text: "  + Base64.encodeBase64String(encrypted));
            return Base64.encodeBase64String(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String randomVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.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[] originalText = cipher.doFinal(Base64.decodeBase64(encrypted));
            System.out.println("decrypted text: "  + new String(originalText));
            return new String(originalText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        String key = "JavasEncryptDemo"; // 128 bit key
        String randomVector = "RandomJavaVector"; // 16 bytes IV
        decrypt(key, randomVector, encrypt(key, randomVector, "Anything you want to encrypt!"));

    }
}

CBCはセキュアモードではなくなりました。パディングは、Oracle攻撃をパディングすることに対して脆弱です。また、文字列でキーとメッセージを処理することは安全ではありません。それらは文字列プールに残り、
ヒープダンプに

2
コメントを感謝します。これは、ユーザーの要求に応じたJavaの暗号化メソッドと復号化メソッドの簡単な例です。質問は約9年前に行われ、それに基づいて回答されました。ありがとう。
viveknaskar 2018年

2
はい、これは暗号化/復号化を導入する簡単な方法のようです。私にとっては魅力のように働きました...ありがとう。
Codewrapper

0

これはコピー/貼り付けソリューションです。@Konstantinoのコードを提供していなくても、その回答を読んで投票することをお勧めします。初期化ベクトル(IV)はソルトのようなものです-秘密にしておく必要はありません。私はGCMを使い始めたばかりですが、AADはオプションであり、特定の状況でのみ使用されます。キーを環境変数に設定しますSECRET_KEY_BASEKeePassなどを使用して、32文字のパスワードを生成します。このソリューションは、私のRubyソリューションをモデルにしています。

    public static String encrypt(String s) {
        try {
            byte[] input = s.getBytes("UTF-8");
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            // generate IV
            SecureRandom secureRandom = SecureRandom.getInstanceStrong();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            secureRandom.nextBytes(ivBytes);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes); // 96 bit tag length
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
            // generate AAD
//          byte[] aadBytes = new byte[cipher.getBlockSize()];
//          secureRandom.nextBytes(aadBytes);
//          cipher.updateAAD(aadBytes);
            // encrypt
            byte[] encrypted = cipher.doFinal(input);
            byte[] returnBytes = new byte[ivBytes.length + encrypted.length];
//          byte[] returnBytes = new byte[ivBytes.length + aadBytes.length + encrypted.length];
            System.arraycopy(ivBytes, 0, returnBytes, 0, ivBytes.length);
//          System.arraycopy(aadBytes, 0, returnBytes, ivBytes.length, aadBytes.length);
            System.arraycopy(encrypted, 0, returnBytes, ivBytes.length, encrypted.length);
//          System.arraycopy(encrypted, 0, returnBytes, ivBytes.length+aadBytes.length, encrypted.length);
            String encryptedString = Base64.getEncoder().encodeToString(returnBytes);
            return encryptedString;
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "encrypt()", "Could not encrypt string: " + e.getMessage());
            return null;
        }
    }

    public static String decrypt(String s) {
        if (s == null || s.length() == 0) return "";
        try {
            byte[] encrypted = Base64.getDecoder().decode(s);
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            System.arraycopy(encrypted, 0, ivBytes, 0, ivBytes.length);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
//          cipher.updateAAD(encrypted, ivBytes.length, cipher.getBlockSize());
            byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize(), encrypted.length - cipher.getBlockSize());
//          byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize()*2, encrypted.length - cipher.getBlockSize()*2);
            String decryptedString = new String(decrypted, "UTF-8");
            return decryptedString;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "decrypt()", "Could not decrypt string: " + e.getMessage());
            return null;
        }
    }

次に例を示します。

    String s = "This is a test.";
    String enc = Utils.encrypt(s);
    System.out.println(enc);
    // fQHfYjbD+xAuN5XzH2ojk/EWNeKXUrKRSfx8LU+5dpuKkM/pueCMBjKCZw==
    String dec = Utils.decrypt(enc);
    System.out.println(dec);
    // This is a test.

-4

暗号化/復号化コードの生成を行うための自動化ツールを検討する必要があるかもしれません。 https://www.stringencrypt.com/java-encryption/

文字列またはファイルの暗号化に対して毎回異なる暗号化および復号化コードを生成できます。

RSA、AESなどを使用せずに文字列を高速に暗号化する場合は、非常に便利です。

結果の例:

// encrypted with https://www.stringencrypt.com (v1.1.0) [Java]
// szTest = "Encryption in Java!"
String szTest = "\u9E3F\uA60F\uAE07\uB61B\uBE1F\uC62B\uCE2D\uD611" +
                "\uDE03\uE5FF\uEEED\uF699\uFE3D\u071C\u0ED2\u1692" +
                "\u1E06\u26AE\u2EDC";

for (int iatwS = 0, qUJQG = 0; iatwS < 19; iatwS++)
{
        qUJQG = szTest.charAt(iatwS);
        qUJQG ++;
        qUJQG = ((qUJQG << 5) | ( (qUJQG & 0xFFFF) >> 11)) & 0xFFFF;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 6) | (qUJQG << 10)) & 0xFFFF;
        qUJQG ^= iatwS;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 3) | (qUJQG << 13)) & 0xFFFF;
        qUJQG ^= 0xFFFF;
        qUJQG ^= 0xB6EC;
        qUJQG = ((qUJQG << 8) | ( (qUJQG & 0xFFFF) >> 8)) & 0xFFFF;
        qUJQG --;
        qUJQG = (((qUJQG & 0xFFFF) >> 5) | (qUJQG << 11)) & 0xFFFF;
        qUJQG ++;
        qUJQG ^= 0xFFFF;
        qUJQG += iatwS;
        szTest = szTest.substring(0, iatwS) + (char)(qUJQG & 0xFFFF) + szTest.substring(iatwS + 1);
}

System.out.println(szTest);

社内で常時使用しています。


これはあいまいさによるセキュリティであり、実際には安全ではありません。
クロエ

この質問は、静的に文字列を抽出することを困難にするための難読化だけでなく、AESのような実際の最新の暗号強度の暗号化を求めています。これはキャラクター間の状態を維持するようにも見えないため、周波数分析の影響を受けやすくなっています。(ラテン文字のアルファベットの代わりにUTF-16コードポイントを除いて、単一のアルファベットの置換暗号。ただし、英語のASCIIテキストでそれを使用すると、私がこれを誤解しない限り、いくつかの一意の16ビット文字値しか取得できません)
ピーターCordes

-4
String s1="arshad"; 
char[] s2=s1.toCharArray(); 
int s3= s2.length; 

  System.out.println(s3);
 int i=0; 

// for(int j=0;j<s3;j++) 
// System.out.println(s2[j]); 

for(i=0;i<((s3)/2);i++) { 

char z,f=10; 
z=(char) (s2[i] * f); 
s2[i]=s2[(s3-1)-i]; 
s2[(s3-1)-i]=z; 

String b=new String(s2);

 print(b);  }

形式的には、データを読み取り不可能な形式に暗号化します。復号化するには同じコードを使用します。そして、s [i] * fをs [I] / fに変更します。
Arshad shaik 2017

これはあいまいさによるセキュリティであり、実際には安全ではありません。
クロエ

-5
public static String encryptParams(String myTextInput) {

        String myKey = "40674244454045cb9a70040a30e1c007";
        String myVector = "@1B2c3D4e5F6g7H8";

        String encData = "";

        try{
            JavaEncryprtionUtil encUtil = new JavaEncryprtionUtil();
            encData = Base64.encodeToString(encUtil.encrypt(myTextInput.getBytes("UTF-8"), myKey.getBytes("UTF-8"), myVector.getBytes("UTF-8")),Base64.DEFAULT);
            System.out.println(encData);
        }catch(NoSuchAlgorithmException ex){
            ex.printStackTrace();
        }catch(NoSuchPaddingException ex){
            ex.printStackTrace();
        }catch(InvalidKeyException ex){
            ex.printStackTrace();
        }catch(InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
        }catch(IllegalBlockSizeException ex){
            ex.printStackTrace();
        }catch(BadPaddingException ex){
            ex.printStackTrace();
        }catch(UnsupportedEncodingException ex){
            ex.printStackTrace();
        }

        return encData;
    }

1
JavaEncryprtionUtilはJDK APIの一部ですか?そうでない場合は、ライブラリの名前を入力する必要があります。
フェルマーのリトルスチューデント

4
そのクラスが見つかりません。答えが構成されているように感じます。
james.garriss
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.