iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 3
0
Security

安全地寫 Java 的「基本功」系列 第 3

安全地寫 Java 的 「基本功」- Day 2

前言

在前文,我們已經很簡單地講完了資訊安全領域裡的基本概念。是的,這只是基本的,而且很簡單。我不是什麼資安專家,只是一個跟各位一樣,在資訊圈子裡面,為著生活奮鬥的工程師。然而,工程師是什麼,軟體開發又是什麼呢?

對各位而言,軟體的品質應該有哪些項目呢?

  • 軟體應該有正確的功能性?對不?若你買的計算機,3.1415926 * 10 * 10 算出來是 314.1999999,那你可能會把它從十樓摔下去。功能性是我們評估軟體品質的首選指標。
  • 軟體應該有好的效率與速度?若你買了會計系統,跑報表的速度,不如你用 Excel 自己算時,那麼這家會計系統的客服就糟了。我們依賴著軟體為我們做事,多半是因為它可以省下不少時間。
  • 軟體應該要使用起來有好的 kimochi 吧?若你上部落格,想發文卻只能使用純文字,而且版面很醜,那你一定不敢把這個部落格公佈給你的朋友們知道。UI/UX/Usability 雖然是很主觀的事情,但是它卻是對使用者而言的第一印象。

等!我們不是要討論 Security 嗎?

再回過頭來,若是你採購的軟體,例如你的會計系統好了,業務人員登入進來,應該只能進行報帳、銷帳之類的作業,結果他竟然能看到會計人員的功能,並且修改之前傳票的數字。這想起來,感覺挺可怕的。然而,一個不安全的系統,就像把房子蓋在基隆海邊的沙地上,它可以讓你看起來很浪漫,但絕對沒辦法讓你長治久安。

起來吧,讓你的系統多加點安全機制吧!

但我們要怎麼做呢?

起點

使徒保羅有一回在與奧林巴司上的人辯論,他講到上帝造了人之後,給他們定了年歲與居住的疆界。的確,我們是生活在受到時間與空間限制的世界裡,也因此,我們在分析事情時,常需要從時間與空間層面來考慮。

如同上一篇所說,我們從不同的安全認證機構所界定的標準,可以發現,資訊安全是可以由外而內、一層一層地控管、實踐的。但通常,最上層、最外層,屬於企業精神與治理層面的事,不是我們所能決定的。甚至有的網站平台,自己經營電商,自己卻不看重資訊安全。

但我們可以在時間的過程中,慢慢地推進。你可以在每次的函式呼叫裡,多做一些檢查,進行防禦式開發。你也可以在每個系統的設計規格中,更謹慎地使用第三方元件,即使是從 Maven 載下來的也是。

甚至,你可以在設計過程中,用 Sequence Diagram 分析每個行為的時間順序,以找出嚴謹的驗證機制。

有些事能做,有些事不能做,工作中,不就是這樣地充滿著無可奈何嗎?

即使是強調安全了,也常常是犧牲了便利性與使用性。在這限制與放行之間,決策者應如何權衡並調配呢?

週末專案

到了週末,我們或許可以進行一些特別一點的專案,針對某些主題進行深入的探討。本週,我們針對機密性,來做深入的討論。

上篇的程式,講到密碼設計原則。就如同隔壁樓的密碼學所言,傳統資料加密的主要方法,是替換與移位。但我們在實務上,我們不會用密碼產生器那種簡單的方式來為重要的資料加密。而是會用一些進階(advanced)一點的方式,例如:AES (Advanced Encryption Standard)。

假設,你有一個密碼,不要告訴我。我們拿它,來為一個檔案加密。你會怎麼做?

Test First

package javaxx.compress;

import java.io.File;
import java.net.URISyntaxException;
import java.nio.file.Paths;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/**
 * JzipTest
 * cchuang, 2016/12/3.
 */
public class JzipTest {

    private static final String TEST_ORIG_FILE = "/test.txt";
    private static final String TEST_ZIP_FILE = "test.txt.zip";
    private static final String TEST_PASSWORD = "a weak password";

    private File origFile;
    private File zippedFile;

    @Before
    public void setUp() throws Exception {
        origFile = getTestFile(TEST_ORIG_FILE);
        zippedFile = origFile.toPath()
                             .getParent()
                             .resolve(TEST_ZIP_FILE)
                             .toFile();
    }

    private File getTestFile(String testFile) throws URISyntaxException {
        return Paths.get(this.getClass().getResource(testFile).toURI()).toFile();
    }

    @Test
    public void testZipFile() throws Exception {
        Jzip.getInstance()
            .setTarget(zippedFile)
            .addSource(origFile)
            .setPassword(TEST_PASSWORD)
            .zip();

        Assert.assertTrue(zippedFile.exists());

        zippedFile.delete();
    }
}

原理

其實這很簡單,準備一個原始檔,以及它的壓縮檔檔名,然後呼叫 Jzip,把它加進去,並且設定密碼,然後壓縮。我想,把一個檔案加密起來,大家最常用的,應該是把它壓縮起來。通常我會直接用 7zip,如果每次壓縮都要寫個程式,那老闆大概想叫我走路了。

然而,在 Java 裡面,並沒有官方 API 支援加密壓縮。如果你要用平常愛用的 7zip,那麼你必須使用 7zip Binding 搭配上難用的 JNI 機制來呼叫 7zip 的 C++ 函式庫。還有什麼選擇呢?

我們在 Maven Repository 上,可以看到這兩個的更新時間:

然而,奇怪的是,Commons Compress 似乎不支援 AES256 的加密機制。於是,我們來使用 Zip4j 吧。

主要程式

package javaxx.compress;

import java.io.File;
import java.util.ArrayList;

import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;

/**
 * Jzip
 * cchuang, 2016/12/3.
 */
public class Jzip {
    public static Jzip getInstance() {
        return new Jzip();
    }

    private ArrayList<File> sourceFiles;
    private File targetFile;
    private String password;

    private Jzip() {
        this.sourceFiles = new ArrayList<>();
    }

    public Jzip setTarget(File targetFile) {
        this.targetFile = targetFile;
        return this;
    }

    public Jzip addSource(File sourceFile) {
        this.sourceFiles.add(sourceFile);
        return this;
    }

    public Jzip setPassword(String password) {
        this.password = password;
        return this;
    }

    public void zip() {
        try {
            ZipFile targetZip = new ZipFile(targetFile);

            ZipParameters param = prepareZipParameter();

            targetZip.addFiles(sourceFiles, param);
        } catch (ZipException e) {
            e.printStackTrace(System.err);
        }
    }

    private ZipParameters prepareZipParameter() {
        ZipParameters param = new ZipParameters();
        param.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
        param.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);

        if (!isEmpty(this.password)) {
            param.setEncryptFiles(true);
            param.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES);
            param.setAesKeyStrength(Zip4jConstants.AES_STRENGTH_256);
            param.setPassword(this.password);
        }

        return param;
    }

    private boolean isEmpty(String password) {
        return null == this.password || "".equals(this.password.trim());
    }
}

小結

本文的程式可以參考 這篇文章。其實也是一個很簡單的功能,然而我們在實務上使用時,更可以搭配許多包裝的技術,來強化程式的安全性。實作的程式,在 EncryptAndZip 這個 Branch 裡。


上一篇
安全地寫 Java 的 「基本功」- Day 1
下一篇
安全地寫 Java 的 「基本功」- Day 3
系列文
安全地寫 Java 的「基本功」14

尚未有邦友留言

立即登入留言