5

Blowfish を使用してテキスト ファイルの内容を暗号化する Java コードを使用しています。暗号化されたファイルを元に戻す (つまり、復号化する) と、文字列の末尾から 1 文字が失われます。理由はありますか?私はJavaに非常に慣れていないため、何時間もこれをいじっていますが、運がありません。

ファイル war_and_peace.txt には、「This is some text」という文字列だけが含まれています。decrypted.txt には、「This is some tex」が含まれています (最後に t はありません)。Javaコードは次のとおりです。

public static void encrypt(String key, InputStream is, OutputStream os) throws Throwable {
    encryptOrDecrypt(key, Cipher.ENCRYPT_MODE, is, os);
}

public static void decrypt(String key, InputStream is, OutputStream os) throws Throwable {
    encryptOrDecrypt(key, Cipher.DECRYPT_MODE, is, os);
}

private static byte[] getBytes(String toGet)
{
    try
    {
        byte[] retVal = new byte[toGet.length()];
        for (int i = 0; i < toGet.length(); i++)
        {
            char anychar = toGet.charAt(i);
            retVal[i] = (byte)anychar;
        }
        return retVal;
    }catch(Exception e)
    {
        String errorMsg = "ERROR: getBytes :" + e;
        return null;
    }
}

public static void encryptOrDecrypt(String key, int mode, InputStream is, OutputStream os) throws Throwable {


   String iv = "12345678";
   byte[] IVBytes = getBytes(iv);
   IvParameterSpec IV = new IvParameterSpec(IVBytes);


    byte[] KeyData = key.getBytes(); 
    SecretKeySpec blowKey = new SecretKeySpec(KeyData, "Blowfish"); 
    //Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
    Cipher cipher = Cipher.getInstance("Blowfish/CBC/NoPadding");

    if (mode == Cipher.ENCRYPT_MODE) {
        cipher.init(Cipher.ENCRYPT_MODE, blowKey, IV);
        CipherInputStream cis = new CipherInputStream(is, cipher);
        doCopy(cis, os);
    } else if (mode == Cipher.DECRYPT_MODE) {
        cipher.init(Cipher.DECRYPT_MODE, blowKey, IV);
        CipherOutputStream cos = new CipherOutputStream(os, cipher);
        doCopy(is, cos);
    }
}

public static void doCopy(InputStream is, OutputStream os) throws IOException {
    byte[] bytes = new byte[4096];
    //byte[] bytes = new byte[64];
    int numBytes;
    while ((numBytes = is.read(bytes)) != -1) {
        os.write(bytes, 0, numBytes);
    }
    os.flush();
    os.close();
    is.close();
}   

public static void main(String[] args) {


    //Encrypt the reports
    try {
        String key = "squirrel123";

        FileInputStream fis = new FileInputStream("war_and_peace.txt");
        FileOutputStream fos = new FileOutputStream("encrypted.txt");
        encrypt(key, fis, fos);

        FileInputStream fis2 = new FileInputStream("encrypted.txt");
        FileOutputStream fos2 = new FileOutputStream("decrypted.txt");
        decrypt(key, fis2, fos2);
    } catch (Throwable e) {
        e.printStackTrace();
    }
}

`

4

4 に答える 4

7

ここには、最適ではないことがいくつかあります。

しかし、まずあなたの問題を解決しましょう。入力の最後の部分が何らかの理由で欠落している理由は、指定したパディングです: none! パディングを指定しないと、 はCipher完全な長さのブロック (Blowfish の場合は 8 バイト) で動作します。1 ブロック未満の長さの余分な入力は黙って破棄され、欠落しているテキストが表示されます。詳細: 「This is some text」の長さは 17 バイトであるため、2 つの完全なブロックが復号化され、最後の 17 番目のバイト「t」が破棄されます。

常に対称ブロック暗号と組み合わせてパディングを使用してください。PKCS5Padding は問題ありません。

次に、 を使用する場合、独自のCipherものを実装する必要はありません。すでに実装されています。バイトを取得するときと、後でバイトから再構築するときは、必ず同じ文字エンコーディングで操作してください。これはエラーの一般的な原因です。getBytes()String#getBytesString

JCE docsを参照してください。よくある間違いを回避するのに役立ちます。

たとえば、対称暗号化では文字列キーを直接使用することはできません。十分なエントロピーが含まれていないため、そのようなキーをブルート フォースするのが容易になります。JCE はKeyGeneratorクラスを提供します。自分が何をしているのかを正確に理解していない限り、常にそれを使用する必要があります。適切なサイズの安全でランダムなキーを生成しますが、さらに、それは人々が忘れがちなことであり、弱いキーを作成しないことも保証します. たとえば、実際の使用では避けるべき既知の弱いキーが Blowfish にあります。

最後に、CBC 暗号化を行う場合は決定論的 IV を使用しないでください。これを悪用してメッセージを完全に回復させる最近の攻撃がいくつかありますが、これは明らかにクールではありません。SecureRandomIV は、予測不能にするために、常に ( を使用して) ランダムに選択する必要があります。Cipherはデフォルトでこれを行います. で暗号化した後、使用された IV を簡単に取得できますCipher#getIV.

別の注意として、セキュリティ関連性は低くなりますfinally。ブロック内のストリームを閉じて、必ず閉じられるようにする必要があります。そうしないと、例外が発生した場合にファイルハンドルが開いたままになります。

これらすべての側面を考慮したコードの更新バージョンを次に示します ( main.

private static final String ALGORITHM = "Blowfish/CBC/PKCS5Padding";

/* now returns the IV that was used */
private static byte[] encrypt(SecretKey key, 
                              InputStream is, 
                              OutputStream os) {
    try {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        CipherInputStream cis = new CipherInputStream(is, cipher);
        doCopy(cis, os);
        return cipher.getIV();
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

private static void decrypt(SecretKey key, 
                            byte[] iv, 
                            InputStream is, 
                            OutputStream os) 
{
    try {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
        CipherInputStream cis = new CipherInputStream(is, cipher);
        doCopy(cis, os);
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

private static void doCopy(InputStream is, OutputStream os) 
throws IOException {
    try {
        byte[] bytes = new byte[4096];
        int numBytes;
        while ((numBytes = is.read(bytes)) != -1) {
            os.write(bytes, 0, numBytes);
        }
    } finally {
        is.close();
        os.close();
    }
}

public static void main(String[] args) {
    try {
        String plain = "I am very secret. Help!";

        KeyGenerator keyGen = KeyGenerator.getInstance("Blowfish");
        SecretKey key = keyGen.generateKey();
        byte[] iv;

        InputStream in = new ByteArrayInputStream(plain.getBytes("UTF-8"));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        iv = encrypt(key, in, out);

        in = new ByteArrayInputStream(out.toByteArray());
        out = new ByteArrayOutputStream();
        decrypt(key, iv, in, out);

        String result = new String(out.toByteArray(), "UTF-8");
        System.out.println(result);
        System.out.println(plain.equals(result)); // => true
    } catch (Exception e) {
        e.printStackTrace();
    }
}
于 2012-05-27T16:39:23.750 に答える
2

あなたはあなたCipherInputStreamCipherOutputStream混同しています。暗号化するには、プレーンな入力ストリームから読み取り、CipherOutputStream. 解読するには...あなたはアイデアを得ます。

編集:

NOPADDING を指定、CipherInputStream を使用して暗号化しようとしているということです。最初の 16 バイトは 2 つの有効な完全なブロックを形成するため、正しく暗号化されます。その後、1 バイトだけが残り、CipherInputStream クラスがファイルの終わりの指示Cipher.doFinal()を受け取ると、暗号オブジェクトに対して a を実行し、IllegalBlockSizeException を受け取ります。この例外は飲み込まれ、read はファイルの終わりを示す -1 を返します。ただし、PKCS5PADDING を使用すると、すべてが機能するはずです。

編集2:

NOPADDING オプションを指定して CipherStream クラスを使用するのは扱いにくく、エラーが発生しやすいというのが本当の問題であるという点で、emboss は正しいです。実際、これらのクラスは、基礎となる Cipher インスタンスによってスローされたすべての Security 例外を静かに飲み込むことを明示的に示しているため、初心者にはおそらく適していません。

于 2012-05-27T16:53:54.800 に答える
1

キーはバイナリであり、Stringバイナリ データのコンテナーではありません。byte[] を使用します。

于 2012-05-27T09:39:15.117 に答える
1

この問題が発生したとき、暗号で doFinal を呼び出す必要がありました。

http://docs.oracle.com/javase/1.4.2/docs/api/javax/crypto/Cipher.html#doFinal ()

于 2012-05-27T09:45:49.547 に答える