14

暗号化されたログファイルを生成するある種のロガーを書いています。残念ながら、暗号化は私の強みではありません。これで、いくつかのメッセージをファイルに書き込んでから、ファイルを閉じることができます。次に、それを開いて、いくつかのメッセージを追加し、もう一度閉じて、復号化した後、ファイルの途中にパディングバイトが表示されます。暗号化されたファイルを、メッセージを追加するたびに復号化せずに操作する方法はありますか?

編集:もう少し詳細。現在の実装はCipherOutputStreamを利用しています。私が理解しているように、それを使用することを求める方法はありません。出力データサイズがブロックサイズで割り切れるのを制御する場合、「NoPadding」オプションを使用できますか?

4

5 に答える 5

17

CBCモードでAESを使用している場合は、最後から2番目のブロックをIVとして使用して、最後のブロックを復号化できます。最後のブロックは部分的にしかいっぱいになっていない可能性があります。次に、最後のブロックのプレーンテキストに続いて新しいプレーンテキストを暗号化します。

概念実証は次のとおりです。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

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


public class AppendAES {

    public static void appendAES(File file, byte[] data, byte[] key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        RandomAccessFile rfile = new RandomAccessFile(file,"rw");
        byte[] iv = new byte[16];
        byte[] lastBlock = null;
        if (rfile.length() % 16L != 0L) {
            throw new IllegalArgumentException("Invalid file length (not a multiple of block size)");
        } else if (rfile.length() == 16) {
            throw new IllegalArgumentException("Invalid file length (need 2 blocks for iv and data)");
        } else if (rfile.length() == 0L) { 
            // new file: start by appending an IV
            new SecureRandom().nextBytes(iv);
            rfile.write(iv);
            // we have our iv, and there's no prior data to reencrypt
        } else { 
            // file length is at least 2 blocks
            rfile.seek(rfile.length()-32); // second to last block
            rfile.read(iv); // get iv
            byte[] lastBlockEnc = new byte[16]; 
                // last block
                // it's padded, so we'll decrypt it and 
                // save it for the beginning of our data
            rfile.read(lastBlockEnc);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
            lastBlock = cipher.doFinal(lastBlockEnc);
            rfile.seek(rfile.length()-16); 
                // position ourselves to overwrite the last block
        } 
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
        byte[] out;
        if (lastBlock != null) { // lastBlock is null if we're starting a new file
            out = cipher.update(lastBlock);
            if (out != null) rfile.write(out);
        }
        out = cipher.doFinal(data);
        rfile.write(out);
        rfile.close();
    }

    public static void decryptAES(File file, OutputStream out, byte[] key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        // nothing special here, decrypt as usual
        FileInputStream fin = new FileInputStream(file);
        byte[] iv = new byte[16];
        if (fin.read(iv) < 16) {
            throw new IllegalArgumentException("Invalid file length (needs a full block for iv)");
        };
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
        byte[] buff = new byte[1<<13]; //8kiB
        while (true) {
            int count = fin.read(buff);
            if (count == buff.length) {
                out.write(cipher.update(buff));
            } else {
                out.write(cipher.doFinal(buff,0,count));
                break;
            }
        }
        fin.close();
    }

    public static void main(String[] args) throws Exception {
        byte[] key = new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
        for (int i = 0; i<1000; i++) {
            appendAES(new File("log.aes"),"All work and no play makes Jack a dull boy. ".getBytes("UTF-8"),key);
        }
        decryptAES(new File("log.aes"), new FileOutputStream("plain.txt"), key);
    }

}

出力は、すべてを1回の実行で暗号化することによって生成されるものと同じであることを指摘したいと思います。これは暗号化のカスタム形式ではありません---標準のAES/CBC/PKCS5Paddingです。唯一の実装固有の詳細は、空白のファイルの場合、データを開始する前にivを書き込んだことです。

CipherOutputStream編集::を使用して(私の好みのために)改善されたソリューション

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class AppendAES {
    public static CipherOutputStream appendAES(File file, SecretKeySpec key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        return appendAES(file, key, null);
    }

    public static CipherOutputStream appendAES(File file, SecretKeySpec key, SecureRandom sr) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        RandomAccessFile rfile = new RandomAccessFile(file,"rw");
        byte[] iv = new byte[16];
        byte[] lastBlock = null;
        if (rfile.length() % 16L != 0L) {
            throw new IllegalArgumentException("Invalid file length (not a multiple of block size)");
        } else if (rfile.length() == 16) {
            throw new IllegalArgumentException("Invalid file length (need 2 blocks for iv and data)");
        } else if (rfile.length() == 0L) { 
            // new file: start by appending an IV
            if (sr == null) sr = new SecureRandom();
            sr.nextBytes(iv);
            rfile.write(iv);
        } else { 
            // file length is at least 2 blocks
            rfile.seek(rfile.length()-32);
            rfile.read(iv);
            byte[] lastBlockEnc = new byte[16];
            rfile.read(lastBlockEnc);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
            lastBlock = cipher.doFinal(lastBlockEnc);
            rfile.seek(rfile.length()-16);
        } 
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
        byte[] out;
        if (lastBlock != null) {
            out = cipher.update(lastBlock);
            if (out != null) rfile.write(out);
        }
        CipherOutputStream cos = new CipherOutputStream(new FileOutputStream(rfile.getFD()),cipher);
        return cos;
    }

    public static CipherInputStream decryptAES(File file, SecretKeySpec key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        FileInputStream fin = new FileInputStream(file);
        byte[] iv = new byte[16];
        if (fin.read(iv) < 16) {
            throw new IllegalArgumentException("Invalid file length (needs a full block for iv)");
        };
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
        CipherInputStream cis = new CipherInputStream(fin,cipher);
        return cis;
    }

    public static void main(String[] args) throws Exception {
        byte[] keyBytes = new byte[]{
            0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
        };
        SecretKeySpec key = new SecretKeySpec(keyBytes,"AES");

        for (int i = 0; i<100; i++) {
            CipherOutputStream cos = appendAES(new File("log.aes"),key);
            cos.write("All work and no play ".getBytes("UTF-8"));
            cos.write("makes Jack a dull boy.  \n".getBytes("UTF-8"));
            cos.close();
        }

        CipherInputStream cis = decryptAES(new File("log.aes"), key);
        BufferedReader bread = new BufferedReader(new InputStreamReader(cis,"UTF-8"));
        System.out.println(bread.readLine());
        cis.close();
    }

}
于 2012-04-24T03:14:06.183 に答える
2

AES はブロック暗号です。つまり、メッセージを文字ごとに暗号化するのではなく、特定のサイズのチャンクになるまでデータを保存してから書き込みます。ログメッセージがブロックサイズと一致しない可能性が高いため、それ自体が問題を引き起こす可能性があります。それが最初の問題です。

2 番目の問題は、「AES」自体が何をしているかを完全に説明していないことです。ブロック暗号はさまざまな「モード」で使用できます ( wikipedia のこの適切な説明を参照してください)。これらのモードの多くは、ストリームの早い段階からの情報を後で来るデータと混合します。これにより暗号化がより安全になりますが、やはり問題が発生します (ファイルを閉じるときと開くときに混在する情報を保存する必要があるため)。

最初の問題を解決するには、ストリーム暗号が必要です。名前から想像できるように、これはデータ ストリームに対して機能します。上記の暗号モードのいくつかは、ブロック暗号をストリーム暗号のように機能させることができることがわかりました。

しかし、ストリーム暗号はおそらく2番目の問題を解決するのに役立ちません.そのためには、追加されたストリームを正しく初期化できるように、使用間で持ち運ぶ必要があるデータをどこかに保存する必要があります.

本当に、これらすべてを尋ねている場合、最終結果が安全であるとどの程度確信できるでしょうか? 上記をガイダンスとして使用しても、多くの間違いを犯す可能性があります。これを行う既存のライブラリを見つけるか、要件を減らしてより単純な問題を解決することをお勧めします(本当に追加する必要がありますか-その場合、新しいファイルを開始できませんか?または、上記のように追加しますさまざまなセクションを見つけることができるように、ファイルに何らかのマーカーを付けますか?)

于 2012-04-23T16:23:19.993 に答える
1

暗号文にデータを追加できるかどうかは、次の 2 つの要因によって決まります。

  1. 暗号化されたデータ内でランダムにシークできる唯一のモードであるため、AES にはカウンター (CTR) モードを使用する必要があります。この場合、暗号化されたデータの最後までシークする必要があります。CTR モードでは、暗号テキストを暗号ブロック サイズにパディングする必要がないことに注意してください。
  2. メッセージ全体を再度フィードしない限り、メッセージ認証コード (MAC) を使用することはできません。暗号化テキストまたはプレーンテキストにしてください。これは仕様によるものです。これができると、MAC が壊れてしまいます。

したがって、あなたがしようとしていることは、認証が必要ない場合にのみ実行できます。ただし、認証なしの暗号化は、敵対者が暗号化されたデータを簡単に変更できるため、まったく無意味です。認証を正気で犠牲にできるユースケースは非常に限られています。

于 2012-04-23T16:39:18.567 に答える
0

メッセージを追加するたびに暗号化を解除することなく、暗号化されたファイルを操作する方法はありますか?

暗号化されたファイルを暗号化すると、一部の方法では復号化できない場合があります。

次の部分が追加されたメッセージであることを示す何らかのインジケータを持つカスタム暗号化を実装できます。このようにして、同じ方法で各メッセージを復号化します。

これも試してみてください https://stackoverflow.com/a/629762/643500

于 2012-04-23T16:14:55.393 に答える