在前文,我們已經很簡單地講完了資訊安全領域裡的基本概念。是的,這只是基本的,而且很簡單。我不是什麼資安專家,只是一個跟各位一樣,在資訊圈子裡面,為著生活奮鬥的工程師。然而,工程師是什麼,軟體開發又是什麼呢?
對各位而言,軟體的品質應該有哪些項目呢?
3.1415926 * 10 * 10
算出來是 314.1999999,那你可能會把它從十樓摔下去。功能性是我們評估軟體品質的首選指標。等!我們不是要討論 Security 嗎?
再回過頭來,若是你採購的軟體,例如你的會計系統好了,業務人員登入進來,應該只能進行報帳、銷帳之類的作業,結果他竟然能看到會計人員的功能,並且修改之前傳票的數字。這想起來,感覺挺可怕的。然而,一個不安全的系統,就像把房子蓋在基隆海邊的沙地上,它可以讓你看起來很浪漫,但絕對沒辦法讓你長治久安。
起來吧,讓你的系統多加點安全機制吧!
但我們要怎麼做呢?
使徒保羅有一回在與奧林巴司上的人辯論,他講到上帝造了人之後,給他們定了年歲與居住的疆界。的確,我們是生活在受到時間與空間限制的世界裡,也因此,我們在分析事情時,常需要從時間與空間層面來考慮。
如同上一篇所說,我們從不同的安全認證機構所界定的標準,可以發現,資訊安全是可以由外而內、一層一層地控管、實踐的。但通常,最上層、最外層,屬於企業精神與治理層面的事,不是我們所能決定的。甚至有的網站平台,自己經營電商,自己卻不看重資訊安全。
但我們可以在時間的過程中,慢慢地推進。你可以在每次的函式呼叫裡,多做一些檢查,進行防禦式開發。你也可以在每個系統的設計規格中,更謹慎地使用第三方元件,即使是從 Maven 載下來的也是。
甚至,你可以在設計過程中,用 Sequence Diagram 分析每個行為的時間順序,以找出嚴謹的驗證機制。
有些事能做,有些事不能做,工作中,不就是這樣地充滿著無可奈何嗎?
即使是強調安全了,也常常是犧牲了便利性與使用性。在這限制與放行之間,決策者應如何權衡並調配呢?
到了週末,我們或許可以進行一些特別一點的專案,針對某些主題進行深入的探討。本週,我們針對機密性,來做深入的討論。
上篇的程式,講到密碼設計原則。就如同隔壁樓的密碼學所言,傳統資料加密的主要方法,是替換與移位。但我們在實務上,我們不會用密碼產生器那種簡單的方式來為重要的資料加密。而是會用一些進階(advanced)一點的方式,例如:AES (Advanced Encryption Standard)。
假設,你有一個密碼,不要告訴我。我們拿它,來為一個檔案加密。你會怎麼做?
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 裡。