使用數(shù)字簽名實(shí)現(xiàn)數(shù)據(jù)庫(kù)記錄防篡改(Java實(shí)現(xiàn))
本文大綱
一、提出問(wèn)題
二、數(shù)字簽名
三、實(shí)現(xiàn)步驟
四、參考代碼
五、后記
六、參考資料
一、提出問(wèn)題
最近在做一個(gè)項(xiàng)目,需要對(duì)一個(gè)現(xiàn)成的產(chǎn)品的數(shù)據(jù)庫(kù)進(jìn)行操作,增加額外的功能。為此,需要對(duì)該產(chǎn)品對(duì)數(shù)據(jù)庫(kù)有什么操作進(jìn)行研究(至于怎么監(jiān)控一個(gè)產(chǎn)品的操作會(huì)引發(fā)什么數(shù)據(jù)庫(kù)操作,以后會(huì)詳細(xì)解說(shuō))。本來(lái)已經(jīng)對(duì)數(shù)據(jù)庫(kù)的操作了如指掌的,無(wú)意中發(fā)現(xiàn)數(shù)據(jù)庫(kù)表里的每條記錄都會(huì)有這樣一個(gè)字段:
這感覺(jué)不妙了,字段名叫signature,顧名思義,就是簽名的意思呀。難道數(shù)據(jù)庫(kù)表中的每條記錄都會(huì)有簽名?也就是說(shuō)如果我不能正確生成簽名,而直接改記錄中的字段,會(huì)被程序認(rèn)為非法篡改了數(shù)據(jù)?那以后我的產(chǎn)品設(shè)計(jì),是否也可采用這種方式來(lái)對(duì)每條記錄做簽名,防止數(shù)據(jù)被非法篡改,例如日志表中的數(shù)據(jù)?抱著這一發(fā)現(xiàn)以及這一連串的問(wèn)題,我進(jìn)行了以下的研究。在這里我將研究整理了一下,分享給大家。
二、數(shù)字簽名
要解決上面的問(wèn)題,首先就要對(duì)最基礎(chǔ)的知識(shí)進(jìn)行了解。這里最基礎(chǔ)的知識(shí),無(wú)疑就是什么是數(shù)字簽名了。很多同學(xué)可能對(duì)這個(gè)名詞并不陌生,但估計(jì)大多數(shù)人都是對(duì)其一知半解,會(huì)把散列、非對(duì)稱(chēng)加密、數(shù)字簽名、數(shù)字證書(shū)的幾個(gè)概念混為一談,造成混亂。所以我先對(duì)相關(guān)概念進(jìn)行解釋?zhuān)偻轮v。如果很熟悉這方面的同學(xué)可以跳過(guò)此部分,但對(duì)于絕大多數(shù)同學(xué)來(lái)說(shuō),不建議這樣做?;A(chǔ)沒(méi)搭好,直接看怎么實(shí)現(xiàn),換了個(gè)說(shuō)法又不知道怎么去做了。要想提高個(gè)人能力,做到舉一反三很重要。
言歸正傳,先對(duì)跟數(shù)字簽名有關(guān)的密碼學(xué)知識(shí)簡(jiǎn)單說(shuō)一下。加密方法分兩大類(lèi),分別是單鑰加密和雙鑰加密,數(shù)字簽名涉及到雙鑰加密。關(guān)于雙鑰加密,主要涉及到以下幾個(gè)要點(diǎn)[1]:
- 雙鑰加密的密鑰有兩把,一把是公開(kāi)的公鑰,一把是不公開(kāi)的私鑰
- 公鑰和私鑰是一一對(duì)應(yīng)的關(guān)系,有一把公鑰就必然有一把與之對(duì)應(yīng)的、獨(dú)一無(wú)二的私鑰,反之亦成立。
- 所有的(公鑰, 私鑰)對(duì)都是不同的。
- 用公鑰可以解開(kāi)私鑰加密的信息,反之亦成立。
- 同時(shí)生成公鑰和私鑰應(yīng)該相對(duì)比較容易,但是從公鑰推算出私鑰,應(yīng)該是很困難或者是不可能的。
- 在雙鑰體系中,公鑰用來(lái)加密信息,私鑰用來(lái)數(shù)字簽名。
- 還有一點(diǎn)關(guān)于數(shù)字證書(shū)的。因?yàn)槿魏稳硕伎梢陨勺约旱墓€私鑰對(duì),所以為了防止有人散布偽造的騙取信任,就需要一個(gè)可靠的第三方機(jī)構(gòu)來(lái)生成經(jīng)過(guò)認(rèn)證的公鑰、私鑰對(duì)。簡(jiǎn)單來(lái)說(shuō),數(shù)字證書(shū)是權(quán)威的第三方機(jī)構(gòu)頒發(fā)的,用來(lái)認(rèn)證某對(duì)公鑰私鑰的證書(shū),經(jīng)過(guò)這個(gè)數(shù)字證書(shū)認(rèn)證的公鑰私鑰,就可以明確屬于某人或者某機(jī)構(gòu),是合法的,可信任的。就如同身份證,是證明你身份的一個(gè)證件。所以數(shù)字證書(shū)跟數(shù)字簽名是兩回事,要分清楚。
數(shù)字簽名,顧名思義,就類(lèi)似于一種寫(xiě)在紙上的普通的物理簽名,不同的是,數(shù)字簽名是電子信息化的,采用雙鑰加密的技術(shù)實(shí)現(xiàn),是一種用于鑒別數(shù)字信息的方法。處理的過(guò)程,簡(jiǎn)單說(shuō)就是將文件內(nèi)容進(jìn)行hash散列,信息發(fā)送者對(duì)散列后的字符串使用私鑰加密,得到的最終字符串就是簽名。然后將得到的簽名字符串添加到文件信息的后面一同發(fā)送出去。接收者獲取到文件信息和簽名后,使用公鑰對(duì)簽名進(jìn)行解密,就得到文件內(nèi)容加密后的hash散列。此時(shí),他可以對(duì)獲取到的文件內(nèi)容做hash散列,與簽名中的hash散列進(jìn)行匹對(duì),從而鑒別出最終獲取信息的真?zhèn)?。主要過(guò)程如這四幅圖所示[2]:
對(duì)文件內(nèi)容進(jìn)行hash散列,生成摘要
對(duì)生成的摘要,使用私鑰進(jìn)行加密,形成簽名
將得到的簽名,附到文件內(nèi)容后部,就想到與簽名簽到文件尾部那樣子
使用公鑰對(duì)簽名進(jìn)行解密,得到摘要,并與獲取到的文件內(nèi)容生成的摘要做對(duì)比,以確定是否被篡改
想了解更詳細(xì)的數(shù)字證書(shū)相關(guān)內(nèi)容,可以訪問(wèn)此地址:http://www.youdzone.com/signature.html。里面解釋得很形象,應(yīng)該一看就明白的了。
三、實(shí)現(xiàn)步驟
看到這里,開(kāi)篇提出的問(wèn)題也就呼之欲出了。沒(méi)錯(cuò),就是使用數(shù)字簽名技術(shù),將數(shù)據(jù)庫(kù)中的重要字段進(jìn)行簽名,將簽名結(jié)果作為記錄的一列存在記錄中。這樣當(dāng)有人入侵?jǐn)?shù)據(jù)庫(kù),惡意修改字段,程序讀數(shù)據(jù)時(shí)拿簽名校驗(yàn)一下,就知道數(shù)據(jù)是否有被修改過(guò)了。
在java.security包中,有很多有用的類(lèi),用以進(jìn)行安全機(jī)制的開(kāi)發(fā)。對(duì)于要?jiǎng)?chuàng)建數(shù)字簽名,我們主要用到以下的接口或類(lèi):
接口名 |
描述 |
PrivateKey |
A private key |
PublicKey |
A public key |
接口
類(lèi)名 |
描述 |
Signature |
The Signature class is used to provide applications the functionality of a digital signature algorithm. |
KeyPair |
This class is a simple holder for a key pair (a public key and a private key) |
KeyPairGenerator |
The KeyPairGenerator class is used to generate pairs of public and private keys. |
類(lèi)
對(duì)于接口和類(lèi)的描述,我直接引用了Oracle上的J2SE 7的API描述[3],就不翻譯成中文了,以防詞不達(dá)意。大家看英文應(yīng)該能更精確的明白其意思。
利用上述的接口和類(lèi),就可以進(jìn)行數(shù)字簽名和驗(yàn)證了,下面分三部分進(jìn)行基本步驟的描述。
第一部分:生成密鑰并存儲(chǔ)
- 生成KeyPairGenerator實(shí)例,并調(diào)用其genKeyPair()方法生成KeyPair對(duì)象。
- 利用ObjectOutputStream實(shí)例,將KeyPair對(duì)象寫(xiě)到文件中,從而把密鑰保存到文件中。
第二部分:進(jìn)行數(shù)字簽名
- 從密鑰文件中讀取KeyPair對(duì)象。
- 調(diào)用KeyPair對(duì)象的getPrivate()和getPublic()方法,分別獲取PrivateKey和PublicKey。
- 利用密鑰的指定算法生成Signature實(shí)例,然后利用PrivateKey和文件內(nèi)容,分別調(diào)用其initSign()和update()方法,最后調(diào)用sign()方法生成數(shù)字簽名。
第三部分:進(jìn)行簽名驗(yàn)證
- 從密鑰文件中讀取KeyPair對(duì)象。
- 調(diào)用KeyPair對(duì)象的getPrivate()和getPublic()方法,分別獲取PrivateKey和PublicKey。
- 利用密鑰的指定算法生成Signature實(shí)例,然后利用PublicKey和文件內(nèi)容,分別調(diào)用其initSign()和update()方法,最后利用數(shù)字簽名調(diào)用verify()方法驗(yàn)證簽名。
四、參考代碼
根據(jù)上面的步驟描述,基本可以寫(xiě)出程序來(lái)了。下面是參考代碼,未必盡善盡美,但是基本功能都體現(xiàn)到了,供你參考。
工程結(jié)構(gòu):
DataSecurity類(lèi):
package com.hzj.security; import java.io.UnsupportedEncodingException; import java.nio.charset.CharsetEncoder; import java.security.KeyPair; import com.hzj.util.StringHelper; public class DataSecurity { private KeyPair keyPair; private static final String KEY_FILE = "/ca.key"; private DataSignaturer dataSignaturer; public DataSecurity() { try { this.keyPair = KeyPairUtil.loadKeyPair(getClass().getResourceAsStream("/ca.key")); this.dataSignaturer = new DataSignaturer(this.keyPair.getPublic(), this.keyPair.getPrivate()); } catch (RuntimeException e) { System.out.println("沒(méi)有找到KeyPair文件[/ca.key]!"); } } /** * 驗(yàn)證數(shù)字簽名 * @param data * @param signs * @return */ public boolean verifySign(String data, String signs) { if ((data == null) || (signs == null)) { System.out.println("參數(shù)為Null"); } boolean verifyOk = false; try { verifyOk = this.dataSignaturer.verifySign(data.getBytes("UTF-8"), StringHelper.decryptBASE64(signs)); } catch (RuntimeException e) { System.out.println("fail!data=" + data + ", sign=" + signs + ", exception:" + e.getMessage()); } catch (UnsupportedEncodingException e) { System.out.println("不支持UTF-8字符集"); } catch (Exception e) { System.out.println("Exception:" + e.getMessage()); } if (!verifyOk) { System.out.println("fail!data=" + data + ", sign=" + signs + ", verifyOk=false!"); } return verifyOk; } /** * 生成數(shù)字簽名 * @param data * @return */ public String sign(String data) { if (data == null) { System.out.println("參數(shù)為Null"); } String sign = null; try { sign = StringHelper.encryptBASE64(this.dataSignaturer.sign(data.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { System.out.println("不支持UTF-8字符集"); } catch (Exception e) { System.out.println(e.getMessage()); } return sign; } }
DataSignaturer類(lèi):
package com.hzj.security; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; public class DataSignaturer { private PrivateKey privateKey; private PublicKey publicKey; public DataSignaturer(PublicKey publicKey, PrivateKey privateKey){ this.privateKey = privateKey; this.publicKey = publicKey; } /** * 進(jìn)行數(shù)字簽名 * @param data * @return */ public byte[] sign(byte[] data) { if (this.privateKey == null) { System.out.println("privateKey is null"); return null; } Signature signer = null; try { signer = Signature.getInstance(this.privateKey.getAlgorithm()); } catch (NoSuchAlgorithmException e) { System.out.println(e.getMessage()); } try { signer.initSign(this.privateKey); } catch (InvalidKeyException e) { System.out.println(e.getMessage()); } try { signer.update(data); return signer.sign(); } catch (SignatureException e) { System.out.println(e.getMessage()); return null; } catch (NullPointerException e) { System.out.println(e.getMessage()); return null; } } /** * 驗(yàn)證數(shù)字簽名 * @param data * @param signature * @return */ public boolean verifySign(byte[] data, byte[] signature) { if (this.publicKey == null) { System.out.println("publicKey is null"); return false; } Signature signer = null; try { signer = Signature.getInstance(this.publicKey.getAlgorithm()); } catch (NoSuchAlgorithmException e) { System.out.println(e.getMessage()); return false; } try { signer.initVerify(this.publicKey); } catch (InvalidKeyException e) { System.out.println(e.getMessage()); return false; } try { signer.update(data); return signer.verify(signature); } catch (SignatureException e) { System.out.println(e.getMessage()); return false; } } }
KeyPair類(lèi):
package com.hzj.security; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; public class KeyPairUtil { // 采用的雙鑰加密算法,既可以用DSA,也可以用RSA public static final String KEY_ALGORITHM = "DSA"; /** * 從輸入流中獲取KeyPair對(duì)象 * @param keyPairStream * @return */ public static KeyPair loadKeyPair(InputStream keyPairStream) { if (keyPairStream == null) { System.out.println("指定的輸入流=null!因此無(wú)法讀取KeyPair!"); return null; } try { ObjectInputStream ois = new ObjectInputStream(keyPairStream); KeyPair keyPair = (KeyPair) ois.readObject(); ois.close(); return keyPair; } catch (Exception e) { System.out.println(e.getMessage()); } return null; } /** * 將整個(gè)KeyPair以對(duì)象形式存儲(chǔ)在OutputStream流中, 當(dāng)然也可以將PublicKey和PrivateKey作為兩個(gè)對(duì)象分別存到兩個(gè)OutputStream流中, * 從而私鑰公鑰分開(kāi),看需求而定。 * @param keyPair 公鑰私鑰對(duì)對(duì)象 * @param out 輸出流 * @return */ public static boolean storeKeyPair(KeyPair keyPair, OutputStream out) { if ((keyPair == null) || (out == null)) { System.out.println("keyPair=" + keyPair + ", out=" + out); return false; } try { ObjectOutputStream oos = new ObjectOutputStream(out); oos.writeObject(keyPair); oos.close(); return true; } catch (FileNotFoundException e) { System.out.println(e.getMessage()); } catch (IOException e) { System.out.println(e.getMessage()); } return false; } /** * 生成KeyPair公鑰私鑰對(duì) * * @return */ public static KeyPair initKeyPair() throws NoSuchAlgorithmException{ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); keyPairGen.initialize(1024); return keyPairGen.genKeyPair(); } /** * 生成密鑰,并存儲(chǔ) * @param out * @return * @throws NoSuchAlgorithmException */ public static boolean initAndStoreKeyPair(OutputStream out) throws NoSuchAlgorithmException { return storeKeyPair(initKeyPair(), out); } }
StringHelper類(lèi):
package com.hzj.util; import sun.misc.BASE64Encoder; import sun.misc.BASE64Decoder; public class StringHelper { /** * BASE64Encoder 加密 * @param data 要加密的數(shù)據(jù) * @return 加密后的字符串 */ public static String encryptBASE64(byte[] data) { BASE64Encoder encoder = new BASE64Encoder(); String encode = encoder.encode(data); return encode; } /** * BASE64Decoder 解密 * @param data 要解密的字符串 * @return 解密后的byte[] * @throws Exception */ public static byte[] decryptBASE64(String data) throws Exception { BASE64Decoder decoder = new BASE64Decoder(); byte[] buffer = decoder.decodeBuffer(data); return buffer; } }
Program類(lèi):
package com.hzj.main; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.security.NoSuchAlgorithmException; import com.hzj.security.DataSecurity; import com.hzj.security.KeyPairUtil; public class Program { public static void main(String[] args) { // 1.生成證書(shū) // File file = new File("ca.key"); // try { // FileOutputStream fileOutputStream = new FileOutputStream(file); // KeyPairUtil.initAndStoreKeyPair(fileOutputStream); // } catch (FileNotFoundException | NoSuchAlgorithmException e) { // e.printStackTrace(); // } // 2.生成數(shù)字簽名 // DataSecurity dataSecurity = new DataSecurity(); // String sign = dataSecurity.sign("大家好"); // System.out.println("sign:" + sign); //3.驗(yàn)證數(shù)字簽名 DataSecurity dataSecurity = new DataSecurity(); boolean result = dataSecurity.verifySign("大家好", "MCwCFCDs3sBw/fXK9flndl0M5lAUiPYFAhR9vyNNc91UiUBxFwK3GzLLjWgTkQ=="); System.out.println("result:" + result); } }
這里需要注意的是,為什么要對(duì)數(shù)字簽名進(jìn)行進(jìn)行Base64編碼呢?這是因?yàn)樯傻臄?shù)字簽名是byte[]型的,無(wú)論對(duì)應(yīng)哪一種字符集來(lái)轉(zhuǎn)化成String,都會(huì)有亂碼出現(xiàn)。所以,采用Base64進(jìn)行編碼,就可以得到一串可見(jiàn)的字符串,方便存儲(chǔ)和重新調(diào)用。
五、后記
寫(xiě)到這里,本文的內(nèi)容就基本上完結(jié)了。有人看到這里就會(huì)問(wèn),這不是說(shuō)數(shù)據(jù)庫(kù)記錄防篡改嘛,一直都在講數(shù)字簽名,究竟怎么個(gè)防篡改?前文已經(jīng)對(duì)數(shù)字簽名的一些基本原理,使用的場(chǎng)景,開(kāi)發(fā)的步驟、代碼等進(jìn)行了描述,開(kāi)篇是也描述了項(xiàng)目中遇到的數(shù)據(jù)庫(kù)中的問(wèn)題。將這些信息綜合起來(lái),應(yīng)該就知道怎么將數(shù)字簽名應(yīng)用到數(shù)據(jù)庫(kù)記錄中來(lái)作為數(shù)據(jù)庫(kù)防篡改的工具了。知道工具怎么用是基礎(chǔ),會(huì)用工具來(lái)完成自己想做的事情,就是進(jìn)階了。祝你步步高升!
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
- JAVA加密算法數(shù)字簽名實(shí)現(xiàn)原理詳解
- Java PDF 添加數(shù)字簽名的實(shí)現(xiàn)方法
- Java實(shí)現(xiàn)的數(shù)字簽名算法RSA完整示例
- 詳解Java數(shù)字簽名提供XML安全
- 淺析java消息摘要與數(shù)字簽名
- Java數(shù)字簽名算法DSA實(shí)例詳解
- Java加密解密和數(shù)字簽名完整代碼示例
- 常用數(shù)字簽名算法RSA與DSA的Java程序內(nèi)實(shí)現(xiàn)示例
- Java2下Applet數(shù)字簽名
- Java加密和數(shù)字簽名編程
- Java 添加數(shù)字簽名到excel及檢測(cè),刪除簽名
相關(guān)文章
C#實(shí)現(xiàn)表格數(shù)據(jù)轉(zhuǎn)實(shí)體的示例代碼
在實(shí)際開(kāi)發(fā)過(guò)程中,特別是接口對(duì)接之類(lèi)的,對(duì)于這種需求是屢見(jiàn)不鮮,現(xiàn)在很多在線平臺(tái)也都提供了像json轉(zhuǎn)實(shí)體、sql轉(zhuǎn)實(shí)體等。本文將用C#實(shí)現(xiàn)這一功能,需要的可以參考一下2022-09-09同步調(diào)用和異步調(diào)用WebService
本文給大家介紹webservice同步調(diào)用和異步調(diào)用,同步調(diào)用就是一個(gè)同步操作會(huì)阻塞整個(gè)當(dāng)前的進(jìn)程,直到這個(gè)操作完成才能執(zhí)行下一段代碼,異步調(diào)用不會(huì)阻塞啟動(dòng)操作的調(diào)用線程,調(diào)用程序必須通過(guò)輪流檢測(cè),或者等待完成信號(hào)來(lái)發(fā)現(xiàn)調(diào)用的完成。小伙伴們跟著小編一起學(xué)習(xí)2015-09-09C#利用Windows自帶gdi32.dll實(shí)現(xiàn)抓取屏幕功能實(shí)例
這篇文章主要介紹了C#利用Windows自帶gdi32.dll實(shí)現(xiàn)抓取屏幕功能,是C#程序設(shè)計(jì)中常見(jiàn)的一個(gè)重要技巧,需要的朋友可以參考下2014-08-08C#調(diào)用dll報(bào)錯(cuò):無(wú)法加載dll,找不到指定模塊的解決
這篇文章主要介紹了C#調(diào)用dll報(bào)錯(cuò):無(wú)法加載dll,找不到指定模塊的解決問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01分享WCF文件傳輸實(shí)現(xiàn)方法---WCFFileTransfer
這篇文章主要介紹了分享WCF文件傳輸實(shí)現(xiàn)方法---WCFFileTransfer,需要的朋友可以參考下2015-11-11C#使用NOPI庫(kù)實(shí)現(xiàn)導(dǎo)入Excel文檔
NPOI中N指代的是.Net,POI是一個(gè)完全開(kāi)源的Java寫(xiě)成的庫(kù),能夠在沒(méi)有安裝微軟Office或者相應(yīng)環(huán)境的情況下讀寫(xiě)Excel、Word等微軟OLE2組件文檔,幾乎支持所有的Office97~Office2007的文件格式。所以NPOI就是POI項(xiàng)目的.Net版本。2017-05-05