探索文件加解密

0. 前言

這篇文章主要介紹加密算法的另外一使用場景——文件加解密。事實上,已於 16 年實現過加密文件的功能並用於公司的項目中。確保安全的前提下,這次分享只介紹些簡單的加密方法和實現過程。更高級深刻的研究還請自行解決。git

前面介紹了常見的加解密算法和 Java 語言的實現,使用場景通常是在數據通訊領域的報文加密。還記的 AliceBob 這對 CP 組合麼?github

DESAES 算法將待加密的數據進行分塊,以 8 字節、16 字節等其餘劃分方式進行分塊加密操做最後合併成須要的明文。 RSA 算法建議只針對少許數據進行加密。當加密趕上文件時,只有對稱加密算法更加適合。算法

1. 分析

文件加解密有三種使用場景,實現加密文件的難度遞增。安全

  1. 將文件一次解密讀入內存或一次加密寫入硬盤。
  2. 邊讀文件邊解密或邊寫文件邊加密。
  3. 對文件進行隨機讀寫操做。

有三種加密方式可用:ide

  1. 線性變換,適用全部場景。
  2. 奇偶置換,適用於 1 和 2 ,勉強適用於 3 。
  3. 分塊加密,適用月 1,勉強適用於 2,很難適用於 3 。

後面 分別介紹三種方式及應用到不一樣場景中的代碼實現。函數

2. 線性變換

線性變換用於單字節處理,因此知足所有三種場景,能夠簡單地表示爲 y=f(x)x 表示加密前的字節,y 表示加密後的字節。加密算法取決於 f(x) 的實現方式。最簡單的如:f(x)=(x+1)%256 ,破解的難度取決於函數 f(x) 的複雜度。this

/** * 線性變換加密方式 */
public class LinearChage {

    /** * 逐個字節處理 * * @param b * @return */
    public byte encrypt(byte b) {
        return (byte) (b + 100);
    }

    /** * 逐個字節處理 * * @param b * @return */
    public byte decrypt(byte b) {
        return (byte) (b - 100);
    }
}
複製代碼

重寫相關的讀文件流中 read 方法。加密

@Override
    public int read() throws IOException {
        // 字節線性變換加密方式
        int b = super.read();
        if (b < 0) // 讀到結尾,不處理
            return b;
        b = linearChage.decrypt((byte) b);
        return b;
    }

    @Override
    public int read(byte[] b) throws IOException {
        return this.read(b, 0, b.length);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {

        // 字節線性變換加密方式
        int length = super.read(b, off, len);
        if (length <=0) // 讀到結尾或讀完,不處理。
            return length;
        for (int i = off; i < off + length; i++) {
            b[i] = linearChage.decrypt(b[i]);
        }
        return length;
    }
複製代碼

重寫相關的讀文件流中 write 方法。spa

@Override
    public void write(int b) throws IOException {
        // 字節線性變換加密方式
        b = linearChage.encrypt((byte) b);
        super.write(b);
    }

    @Override
    public void write(byte[] b) throws IOException {
        // 同帶 off 的方法一塊兒處理
        this.write(b, 0, b.length);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        // 字節線性變換加密方式
        for (int i = off; i < off + len; i++) {
            b[i] = linearChage.encrypt(b[i]);
        }
        super.write(b, off, len);
    }
複製代碼

3. 奇偶置換

奇偶置換用於交換相鄰兩個字節,好比,置換前是 「ab」,置換後是 「ba」,這種方式最大的弱點就是很容易被破解。所以建議同線性變換方式結合使用。勉強適用於 3 主要是由於,每次隨機讀寫的位置不必定是奇數位。code

/** * 奇偶位置換加密方式。 * 加密、解密代碼一致。 */
public class OddExchange {

    /** * 記錄遺留字節內容 */
    private byte left;
    /** * 記錄是否有遺留字節 */
    private boolean hasLeft;

    .... getter setter方法

    /** * 加密交換 * * @param data */
    public void encrypt(byte[] data, int off, int len) {
        for (int i = off; i < off + len; i += 2) {
            byte t = data[i];
            data[i] = data[i + 1];
            data[i + 1] = t;
        }
    }

    /** * 解密交換 * @param data * @param off * @param len */
    public void decrypt(byte[] data, int off, int len) {
        for (int i = off; i < off + len; i += 2) {
            byte t = data[i];
            data[i] = data[i + 1];
            data[i + 1] = t;
        }
    }
}
複製代碼

重寫相關的讀文件流中 read 方法。

@Override
    public int read() throws IOException {
        if (oddExchange.isHasLeft()) {
            oddExchange.setHasLeft(false);
            int b = oddExchange.getLeft();
            return b > 0 ? b : b + 256;
        } else { // 沒有剩餘字節,須要讀取兩次
            int b = super.read();
            if (b < 0) // 讀到結尾,不處理
                return b;
            int next = super.read();
            if (next < 0) // 已經是最後一個字節
                return b;
            byte[] data = new byte[2];
            data[0] = (byte) b;
            data[1] = (byte) next;
            oddExchange.decrypt(data, 0, data.length);
            oddExchange.setHasLeft(true);
            oddExchange.setLeft(data[1]);
            return data[0] > 0 ? data[0] : (data[0] + 256);
        }

    }

    @Override
    public int read(byte[] b) throws IOException {
        return this.read(b, 0, b.length);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        if (len % 2 == 0) { // 但願讀取偶數長度
            int length = super.read(b, off, len);
            oddExchange.decrypt(b, off, length);
            if (oddExchange.isHasLeft()) { // 有剩餘位
                byte t = b[off + length - 1];
                for (int i = off + length - 1; i > off; i--)
                    b[i] = b[i - 1];
                b[off] = oddExchange.getLeft();
                oddExchange.setLeft(t);
            }
            return length;

        } else { // 但願讀取奇數長度
            if (oddExchange.isHasLeft()) { // 有剩餘位少讀 1 字節
                int length = super.read(b, off, len - 1);
                oddExchange.decrypt(b, off, length);
                for (int i = length; i > off; i--) {
                    b[i] = b[i - 1];
                }
                b[off] = oddExchange.getLeft();
                oddExchange.setHasLeft(false);
                return length + 1;
            } else { // 沒有剩餘位,多讀一字節
                byte[] buff = new byte[len + 1]; // 存在越界的可能,因此new一個
                int length = super.read(buff);
                oddExchange.decrypt(buff, 0, length);
                for (int i = 0; i < length - 1; i++) {
                    b[i + off] = buff[i];
                }
                oddExchange.setLeft(buff[length - 1]);
                oddExchange.setHasLeft(true);
                return length - 1;
            }
        }
    }
複製代碼

重寫相關的讀文件流中 write 方法。注意,關閉文件流時,須要將未寫入的字節補寫。

@Override
    public void write(int b) throws IOException {
        if (oddExchange.isHasLeft()) {// 有剩餘的字節
            byte[] data = new byte[2];
            data[0] = oddExchange.getLeft();
            data[1] = (byte) b;
            oddExchange.setHasLeft(false);
            oddExchange.encrypt(data, 0, 2);
            super.write(data[0]);
            super.write(data[1]);
        } else { // 沒有剩餘字節,留做下次一道處理
            oddExchange.setLeft((byte) b);
            oddExchange.setHasLeft(true);
        }

    }

    @Override
    public void write(byte[] b) throws IOException {
        // 同帶 off 的方法一塊兒處理
        this.write(b, 0, b.length);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        if (len % 2 == 0) { // 偶數長度
            if (oddExchange.isHasLeft()) { // 有剩餘字節
                byte t = oddExchange.getLeft();
                oddExchange.setLeft(b[off + len - 1]);
                for (int i = off + len - 1; i > off ; i--) {
                    b[i] = b[i - 1];
                }
                b[off] = t;

            }
            oddExchange.encrypt(b, off, len);
            super.write(b, off, len);

        } else { // 奇數長度
            if (oddExchange.isHasLeft()) { // 有剩餘字節,須要同剩餘字節拼接成偶數長度
                byte[] dest = new byte[off + len + 1];
                dest[off] = oddExchange.getLeft();
                oddExchange.setHasLeft(false);
                for (int i = off; i < off + len; i++) {
                    dest[i + 1] = b[i];
                }
                oddExchange.encrypt(dest, off, len+1);
                super.write(dest, off, len+1);
            } else { // 沒有剩餘字節,保留一個剩餘字節
                oddExchange.encrypt(b, off, len - 1);
                oddExchange.setLeft(b[off + len - 1]);
                oddExchange.setHasLeft(true);
                super.write(b, off, len - 1);
            }
        }

    }

    @Override
    public void close() throws IOException {
        // 關閉流的時候檢查是否有剩餘字節未寫入
        if (oddExchange.isHasLeft()) {
            write(oddExchange.getLeft());
            oddExchange.setHasLeft(false);
        }

        super.close();
    }
複製代碼

4. 分塊加密

其實奇偶置換也是分塊加密的一種特殊場景——塊長等於 2 。所以只須要考慮到當前讀寫到是偶數位仍是奇數位。熟悉的對稱加密算法中,最短的分塊長度是 8 。用於場景 1 不用多作考慮,場景 2 須要考慮以前已經讀寫的長度,若不是 8 的整數倍,須要特殊處理。至於場景 3 ,若您的生物 CPU 很強大,建議嘗試下。

並且,使用分塊加密不要使用 8 的整數倍塊長,(不必定是 8 ,主要取決於你加密算法的塊長),在使用不恰當的填充模式時,8 的整數倍塊長加密後的數據會多 8 字節,加密前文件大小是 1M ,加密後就極可能變成 2M , 通常不被接受哈。

該方案實現起來較爲複雜,暫不提供代碼解釋。

以爲有用?那打賞一個唄。我要打賞

此處是廣告Flueky的技術小站

相關文章
相關標籤/搜索