必要なのは、2Dバーコード(PDF-417)に表示される文字列を暗号化することです。そのため、誰かがスキャンのアイデアを得ると、読み取り可能なものは何もなくなります。
その他の要件:
それは、周りを索する人々を取り除くために十分にシンプルであり、そのデータを取得することに興味がある他の会社のために簡単に解読できなければなりません。彼らは私たちに電話し、標準を伝えたり、解読に使用できる簡単なキーを与えたりします。
おそらくそれらの企業はさまざまなテクノロジーを使用できるので、特別なプラットフォームやテクノロジーに縛られていない標準に固執するのが良いでしょう。
何を指示してるんですか?高度なセキュリティ標準を達成するのにそれほど複雑なことなくencrypt()
とdecrypt()
を実行するJavaクラスがありますか?
警告
これを何らかのセキュリティ測定として使用しないでください。
この投稿の暗号化メカニズムはワンタイムパッドです。これは、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
DES 、 DES 、または AES のような広く利用可能な標準の対称暗号を使用することをお勧めします。それは最も安全なアルゴリズムではありませんが、実装の負荷があり、バーコード内の情報を解読することになっている人にキーを与える必要があります。 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);
これはGoogleを介して表示される最初のページであり、すべての実装のセキュリティの脆弱性により私はうんざりするので、これを投稿して他の暗号化に関する情報を追加します7 Years元の投稿から。コンピューターエンジニアリングで修士号を取得し、暗号の研究と学習に多くの時間を費やしたので、インターネットをより安全にするために2セントを投じています場所。
また、特定の状況では多くの実装が安全である可能性があることに注意してください。しかし、なぜそれらを使用し、潜在的に誤ってミスを犯すのでしょうか?特別な理由がない限り、入手可能な最も強力なツールを使用してください。全体的には、ライブラリを使用し、可能であれば、細かいことは避けてください。
UPDATE 4/5/18:理解しやすくするために一部を書き直し、推奨ライブラリを Jasypt から Googleの新しいライブラリTink 、既存のセットアップから Jasypt を完全に削除することをお勧めします。
まえがき
安全な対称暗号化の基本を以下に概説し、標準Javaライブラリを使用して自分で暗号化を実装するときによく見られるよくある間違いを指摘します。 Googleの新しいライブラリTink に実行されるすべての詳細をスキップしたい場合は、それをプロジェクトにインポートし、すべての暗号化にAES-GCMモードを使用すると、安全になります。
Javaで暗号化する方法についての詳細を学びたい場合:)
ブロック暗号
まず最初に、対称鍵ブロック暗号を選択する必要があります。ブロック暗号は、擬似乱数を作成するために使用されるコンピューター機能/プログラムです。疑似ランダム性とは、量子コンピューター以外のコンピューターでは、それと実際のランダム性の違いを判別できない偽のランダム性です。ブロック暗号は、暗号化のビルディングブロックのようなものであり、異なるモードまたはスキームで使用すると、暗号化を作成できます。
現在利用可能なブロック暗号アルゴリズムに関しては、NEVERを確認し、NEVERDES を使用し、 DES を使用しないでください。 SnowdenのNSAリリースでさえ、可能な限り擬似ランダムに本当に近いことを検証できた唯一のブロック暗号は、 AES 256 です。 AES 128もありますが、AES 256は256ビットブロックで動作しますが、AES 128は128ブロックで動作します。すべてのAES 128は、いくつかの弱点が発見されていますが、すべてが安全であると考えられていますが、256はそれと同じくらい強固です。
楽しい事実 DES は、最初に設立されて実際に数年間秘密を保ったときにNSAによって破壊され、一部の人々はまだ主張しています DES は安全です。 DES の弱点を発見して分析した研究論文がかなりあります。
暗号化モード
暗号化は、ブロック暗号を使用し、特定のスキームを使用すると作成されます。これにより、ランダム性とキーを組み合わせて、キーを知っている限り可逆的なものを作成できます。これは暗号化モードと呼ばれます。
暗号化モードとECBとして知られる最も単純なモードの例を次に示します。これにより、何が起こっているかを視覚的に理解できます。
最も一般的にオンラインで表示される暗号化モードは次のとおりです。
ECB CTR、CBC、GCM
リストされているもの以外にも他のモードが存在し、研究者は既存の問題を改善するために常に新しいモードに取り組んでいます。
次に、実装と安全なものに移りましょう。 NEVER有名な Linuxペンギン .
Javaで実装する場合、次のコードを使用すると、ECBモードがデフォルトで設定されることに注意してください。
Cipher cipher = Cipher.getInstance("AES");
...危険このISは脆弱性です!そして残念なことに、これはStackOverflow全体およびオンラインのチュートリアルと例で見られます。
ナンスとIV
ECBモードで見つかった問題への対応として、IVとしても知られるナウンスが作成されました。アイデアは、新しいランダム変数を生成し、それをすべての暗号化に付加することです。これにより、同じ2つのメッセージを暗号化すると、それらは異なるものになります。この背後にある美しさは、IVまたは一回限りが一般的な知識であるということです。つまり、攻撃者はこれにアクセスできますが、キーを持っていない限り、その知識では何もできません。
一般的な問題は、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」を暗号化すると、暗号化されたメッセージは「eu23ng」のようになり、攻撃者は1ビットを「eu53ng」に変更し、メッセージを解読すると突然「Sell 900」と表示されます。
これを回避するために、インターネットの大部分はGCMを使用しており、HTTPSを見るたびにGCMを使用している可能性があります。 GCMは、暗号化されたメッセージにハッシュで署名し、この署名を使用してメッセージが変更されていないことを確認します。
GCMは複雑であるため、GCMの実装は避けたいと思います。 Googleの新しいライブラリTink を使用する方が良いでしょう。ここでも、誤ってIVを繰り返した場合、GCMの場合にキーを危険にさらすことになります。これは究極のセキュリティ欠陥です。新しい研究者は、IVを繰り返してもキーが危険にさらされないが、これがまだ主流になっていないIVリピート耐性暗号化モードに取り組んでいます。
GCMを実装したい場合、ここに Nice GCM実装へのリンク があります。しかし、私はセキュリティを保証することはできません、またはその適切に実装されている場合、それは基礎を取得します。また、GCMにはパディングがないことに注意してください。
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
キーとパスワード
もう1つの非常に重要な注意点は、暗号化に関しては、キーとパスワードは同じものではないということです。暗号化のキーは、安全であると見なされるために一定量のエントロピーとランダム性を持つ必要があります。そのため、適切な暗号化ライブラリを使用してキーを生成する必要があります。
ここで実際に実行できる実装は2つあります。1つ目は ランダムキー生成用のこのStackOverflowスレッド にあるコードを使用することです。このソリューションでは、安全な乱数ジェネレーターを使用して、使用可能なキーをゼロから作成します。
もう1つの安全性の低いオプションは、パスワードなどのユーザー入力を使用することです。議論した問題は、パスワードに十分なエントロピーがないため、パスワードを取得して強化するアルゴリズムである PBKDF2 を使用する必要があることです。 私が気に入ったStackOverflow実装 です。ただし、Google Tinkライブラリにはこれらすべてが組み込まれているため、利用する必要があります。
Android開発者
ここで指摘する重要な点は、Androidコードがリバースエンジニアリング可能であり、ほとんどの場合、ほとんどのJavaコードもリバースエンジニアリング可能であることです。つまり、パスワードをコード内のプレーンテキストで保存する場合です。ハッカーは簡単に取得できます。通常、これらのタイプの暗号化には、非対称暗号化などを使用します。これはこの投稿の範囲外であるため、ここに飛び込むことは避けます。
2013年からの興味深い読み :AndroidのCrypto実装の88%が不適切に行われたことを指摘しています。
最終的な考え
もう一度、暗号化にJavaライブラリを直接実装することを避け、 Google Tink を使用することをお勧めします。正しく。さらに、Tink githubで指摘された問題をチェックし、あちこちで脆弱性ポップアップを確認してください。
ご質問やフィードバックがある場合は、お気軽にコメントしてください!セキュリティは常に変化しており、それに追いつくために最善を尽くす必要があります:)
おかげで私はあなたのコードを使用してこのクラスを作成しました
オブジェクト暗号
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();
}
}
これが、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;
// }
}
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 oneだけでなく、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(コマンドラインインターフェイス)ツールを提供します。もっと詳しく知る。
Apache Wicketに統合して、安全なアプリケーションのURLのより堅牢な暗号化を実現します。
包括的なガイドとjavadocドキュメント。開発者が自分のデータに対して実際に何を行っているかをよりよく理解できるようにします。
堅牢な文字セットのサポート。元の文字セットがどれであってもテキストを適切に暗号化およびダイジェストするように設計されています。日本語、韓国語、アラビア語などの言語の完全なサポート。エンコードやプラットフォームの問題はありません。
非常に高いレベルの設定機能:開発者は、たとえば暗号化に使用するパスワードをリモートHTTPSサーバーに要求するように「暗号化装置」に指示するなどのトリックを実装できます。セキュリティニーズを満たすことができます。
これはどう:
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;
}
私にとってはうまく機能し、かなりコンパクトです。
これは、次の点を考慮してJava 8で書いたばかりの暗号化および復号化コードです。誰かがこれが役立つことを願っています:
暗号化アルゴリズム:256ビットキーのブロック暗号AESは十分に安全であると見なされます。メッセージ全体を暗号化するには、モードを選択する必要があります。認証された暗号化(機密性と整合性の両方を提供)が推奨されます。 GCM、CCM、およびEAXは、最も一般的に使用される認証済み暗号化モードです。通常、GCMが好まれ、GCM専用の命令を提供するIntelアーキテクチャで良好に機能します。これら3つのモードはすべてCTRベース(カウンターベース)モードであるため、パディングは不要です。結果として、パディング関連の攻撃に対して脆弱ではありません
GCMには初期化ベクトル(IV)が必要です。 IVは秘密ではありません。唯一の要件は、ランダムまたは予測不能でなければなりません。 Javaでは、SecuredRandom
クラスは、暗号的に強力な疑似乱数を生成することを目的としています。擬似乱数生成アルゴリズムは、getInstance()
メソッドで指定できます。ただし、Java 8以降では、Provider
によって構成および提供される最も強力なアルゴリズムを使用するgetInstanceStrong()
メソッドを使用することをお勧めします
NISTは、GCMに96ビットIVを推奨し、相互運用性、効率、および設計の簡素化を促進しています。
追加のセキュリティを確保するために、次の実装では、疑似乱数バイト生成の2 ^ 16バイトごとにSecureRandom
が再シードされます
受信者は、暗号化テキストを復号化できるようにIVを知っている必要があります。したがって、IVは暗号文とともに転送する必要があります。一部の実装では、IVをAD(関連データ)として送信します。これは、暗号タグとIVの両方で認証タグが計算されることを意味します。ただし、これは必須ではありません。意図的な攻撃またはネットワーク/ファイルシステムエラーのためにIVが送信中に変更された場合、認証タグの検証はいずれにせよ失敗するため、IVに暗号テキストを単純に追加することができます
文字列は不変であるため、クリアテキストメッセージまたはキーを保持するために文字列を使用しないでください。したがって、使用後はクリアできません。これらのクリアされていない文字列はメモリ内に残り、ヒープダンプに表示される場合があります。同じ理由で、これらの暗号化または復号化メソッドを呼び出すクライアントは、不要になったメッセージまたはキーを保持するすべての変数または配列をクリアする必要があります。
一般的な推奨事項に従うコードにハードコーディングされているプロバイダーはありません
最後に、ネットワークまたはストレージを介して送信するには、キーまたは暗号テキストをBase64エンコードを使用してエンコードする必要があります。 Base64の詳細は こちら にあります。 Java 8アプローチに従う必要があります
バイト配列は次を使用してクリアできます。
Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);
ただし、Java 8では、SecretKeyspec
およびSecretKey
をクリアする簡単な方法はありません。これら2つのインターフェイスの実装では、インターフェイスDestroyable
のメソッドdestroy()
が実装されていないようです。次のコードでは、リフレクションを使用してSecretKeySpec
およびSecretKey
をクリアする別のメソッドが記述されています。
キーは、次の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
の処理について疑問に思う場合に備えて、そのギャップを埋めようと試みます。
回答の前半で示したように、String
は不変であり、使用後にクリアできないため、String
で機密情報を処理することは一般に良い考えではありません。そして、私たちが知っているように、String
に強力な参照がない場合でも、ガベージコレクターはすぐにそれをヒープから削除しません。したがって、String
は、プログラムからアクセスできない場合でも、未知の時間ウィンドウ内でメモリ内に存在し続けます。それに関する問題は、その時間枠でのヒープダンプが機密情報を明らかにすることです。したがって、すべての機密情報をバイト配列またはchar配列で処理し、目的が達成されたら配列に0を入力することをお勧めします。
ただし、すべての知識があるのに、暗号化される機密情報がString
にある場合、最初にそれをバイト配列に変換し、上記で紹介したencrypt
およびdecrypt
関数を呼び出す必要があります。 (他の入力キーは、上記のコードスニペットを使用して生成できます)。
String
は、次の方法でバイトに変換できます。
byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);
Java 8の時点で、String
は内部的にUTF-16
エンコーディングでヒープに格納されます。ただし、ここではUTF-8
を使用しています。これは、特にASCII文字の場合、UTF-16
よりも少ないスペースで済むためです。
同様に、暗号化されたバイト配列も次のように文字列に変換できます。
String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);
https://www.bouncycastle.org/ のようなものを使用することを検討します。これは事前に構築されたライブラリであり、多くの異なる暗号で好きなものを暗号化できます。スヌーピングからですが、情報を本当に保護したい場合は、Base64を使用しても実際には保護されません。
Javaがサポートするものを読むことができるいくつかのリンクがあります
この例は、大量のデータを(AES、Blowfish、RC2、3DESなどの対称暗号化アルゴリズムを使用して)暗号化する方法を示しています。データは、EncryptBytes、EncryptString、EncryptBytesENC、またはEncryptStringENCのいずれかの暗号化メソッドにチャンクで渡されます。 (メソッド名は、入力のタイプ(文字列またはバイト配列)および戻り値のタイプ(エンコードされた文字列またはバイト配列)を示します。FirstChunkおよびLastChunkプロパティは、チャンクがストリームの最初、中間、または最後であるかどうかを示しますデフォルトでは、FirstChunkとLastChunkの両方がtrueに等しく、渡されたデータが全量であることを意味します。
ここでは、confidentialityおよびintegrityを提供するバイト暗号化のJava.*
およびjavax.crypto.*
依存関係のみを使用した単純なソリューション。 選択された平文攻撃では区別できない キロバイトのオーダーの短いメッセージの場合。
パディングなしでAES
モードでGCM
を使用します。128ビットキーは、多数の反復と、提供されたパスワードからの静的ソルトを使用してPBKDF2
によって導出されます。これにより、ブルートフォースパスワードが困難になり、キー全体にエントロピーが分散されます。
ランダムな初期化ベクトル(IV)が生成され、暗号文の先頭に追加されます。さらに、静的バイト0x01
は、「バージョン」として最初のバイトとして追加されます。
メッセージ全体は、AES/GCM
によって生成されたメッセージ認証コード(MAC)に入ります。
ここでは、confidentialityおよびintegrityを提供するゼロ外部依存暗号化クラスがあります:
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 {
// https://crypto.stackexchange.com/questions/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);
}
}
}
Nice CLIを使用したプロジェクト全体: https://github.com/trichner/tcrypt
編集:適切なencryptString
およびdecryptString
を使用
これがコピー/貼り付けソリューションです。また、コードを提供していない場合でも、 @ Konstantino's answer を読んで投票することをお勧めします。初期化ベクトル(IV)は塩のようなものです-秘密にしておく必要はありません。私はGCMを初めて使用しますが、明らかにAADはオプションであり、特定の状況でのみ使用されます。環境変数SECRET_KEY_BASE
にキーを設定します。 KeePass のようなものを使用して、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.