自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

循序漸進(jìn)學(xué)加密

安全 數(shù)據(jù)安全
上大學(xué)后,有幸聽(tīng)盧開(kāi)澄教授講《計(jì)算機(jī)密碼學(xué)》,才知道原來(lái)我們小時(shí)候玩的這個(gè)游戲遠(yuǎn)遠(yuǎn)不能稱(chēng)之為加密。那么到底什么是加密呢?

還記得上初二的那年夏天,班里來(lái)了一個(gè)新同學(xué),他就住在我家對(duì)面的樓里,于是我們一起上學(xué)放學(xué),很快便成了最要好的朋友。我們決定發(fā)明一套神秘的溝通方式,任何人看到都不可能猜到它的真實(shí)含義。我們第一個(gè)想到的就是漢語(yǔ)拼音,但很顯然光把一個(gè)句子變成漢語(yǔ)拼音是不夠的,于是我們把26個(gè)英文字母用簡(jiǎn)譜的方式從低音到高音排起來(lái),就得到了一個(gè)簡(jiǎn)單的密碼本:

把“我們都是好朋友”用這個(gè)密碼本變換之后就得到了這樣的結(jié)果:

小時(shí)候玩這個(gè)游戲樂(lè)此不疲,覺(jué)得非常有趣。上大學(xué)后,有幸聽(tīng)盧開(kāi)澄教授講《計(jì)算機(jī)密碼學(xué)》,才知道原來(lái)我們小時(shí)候玩的這個(gè)游戲遠(yuǎn)遠(yuǎn)不能稱(chēng)之為加密。那么到底什么是加密呢?

什么是加密?

把字符串123456經(jīng)過(guò)base64變換之后,得到了MTIzNDU2,有人說(shuō)這是base64加密。

把字符串123456經(jīng)過(guò)md5變換之后,得到了E10ADC3949BA59ABBE56E057F20F883E,有人說(shuō)這是md5加密。

從嚴(yán)格意義上來(lái)說(shuō),不管是base64還是md5甚至更復(fù)雜一些的sha256都不能稱(chēng)之為加密。

一句話(huà),沒(méi)有密鑰的算法都不能叫加密。

  • 編碼(Encoding)是把字符集中的字符編碼為指定集合中某一對(duì)象(例如:比特模式、自然數(shù)序列、8位字節(jié)或者電脈沖),以便文本在計(jì)算機(jī)中存儲(chǔ)和通過(guò)通信網(wǎng)絡(luò)的傳遞的方法,常見(jiàn)的例子包括將拉丁字母表編碼成摩爾斯電碼和ASCII。base64只是一種編碼方式。
  • 雜湊(Hashing)是電腦科學(xué)中一種對(duì)資料的處理方法,通過(guò)某種特定的函數(shù)/算法(稱(chēng)為雜湊函數(shù)/算法)將要檢索的項(xiàng)與用來(lái)檢索的索引(稱(chēng)為雜湊,或者雜湊值)關(guān)聯(lián)起來(lái),生成一種便于搜索的資料結(jié)構(gòu)(稱(chēng)為雜湊表)。雜湊算法常被用來(lái)保護(hù)存在資料庫(kù)中的密碼字符串,由于雜湊算法所計(jì)算出來(lái)的雜湊值具有不可逆(無(wú)法逆向演算回原本的數(shù)值)的性質(zhì),因此可有效的保護(hù)密碼。常用的雜湊算法包括md5, sha1, sha256等。
  • 加密(Encryption)是將明文信息改變?yōu)殡y以讀取的密文內(nèi)容,使之不可讀的過(guò)程。只有擁有解密方法的對(duì)象,經(jīng)由解密過(guò)程,才能將密文還原為正??勺x的內(nèi)容。加密分為對(duì)稱(chēng)加密和非對(duì)稱(chēng)加密,對(duì)稱(chēng)加密的常用算法包括DES, AES等,非對(duì)稱(chēng)加密算法包括RSA,橢圓曲線(xiàn)算法等。

在古典加密算法當(dāng)中,加密算法和密鑰都是不能公開(kāi)的,一旦泄露就有被破解的風(fēng)險(xiǎn),我們可以用詞頻推算等方法獲知明文。1972年美國(guó)IBM公司研制的DES算法(Data Encryption Standard)是人類(lèi)歷史上第一個(gè)公開(kāi)加密算法但不公開(kāi)密鑰的加密方法,后來(lái)成為美國(guó)軍方和政府機(jī)構(gòu)的標(biāo)準(zhǔn)加密算法。2002年升級(jí)成為AES算法(Advanced Encryption Standard),我們今天就從AES開(kāi)始入手學(xué)習(xí)加密和解密。

準(zhǔn)備工具

通常情況下,加解密都只需要在服務(wù)端完成就夠了,這也是網(wǎng)上大多數(shù)教程和樣例代碼的情況,但在某種特殊情況下,你需要用一種語(yǔ)言加密而用另一種語(yǔ)言解密的時(shí)候,最好有一個(gè)中立的公正的第三方結(jié)果集來(lái)驗(yàn)證你的加密結(jié)果,否則一旦出錯(cuò),你都不知道是加密算法出錯(cuò)了,還是解密算法出錯(cuò)了,對(duì)此我們是有慘痛教訓(xùn)的,特別是如果一個(gè)公司里,寫(xiě)加密的是前端,用的是js語(yǔ)言,而寫(xiě)解密的是后端,用的是java語(yǔ)言或者php語(yǔ)言或者go語(yǔ)言,則雙方更需要有這樣一個(gè)客觀公正的平臺(tái),否則你們之間必然會(huì)陷入永無(wú)休止的互相指責(zé)的境地,前端說(shuō)自己沒(méi)有錯(cuò),是后端解密解錯(cuò)了,后端說(shuō)解密沒(méi)有錯(cuò),是前端加密寫(xiě)錯(cuò)了,而事實(shí)上是雙方都是菜鳥(niǎo),對(duì)密碼學(xué)一知半解,在這種情況下浪費(fèi)的時(shí)間就更多。

在線(xiàn)AES加密解密就是這樣的一個(gè)工具網(wǎng)站,你可以在上面驗(yàn)證你的加密結(jié)果,如果你加密得到的結(jié)果和它的結(jié)果完全一致,就說(shuō)明你的加密算法沒(méi)有問(wèn)題,否則你就去調(diào)整,直到和它的結(jié)果完全一致為止。反之亦然,如果它能從一個(gè)密文解密解出來(lái),而你的代碼解不出來(lái),那么一定是你的算法有問(wèn)題,而不可能是數(shù)據(jù)的問(wèn)題。

我們先在這個(gè)網(wǎng)站上對(duì)一個(gè)簡(jiǎn)單的字符串123456進(jìn)行加密。

下面我們對(duì)網(wǎng)站上的所有選項(xiàng)逐個(gè)解釋一下:

  • AES加密模式:這里我們選擇的是ECB(ee cc block)模式。這是AES所有模式中最簡(jiǎn)單也是最不被人推薦的一種模式,因?yàn)樗墓潭ǖ拿魑膶?duì)應(yīng)的是固定的密文,很容易被破解。但是既然是練習(xí)的話(huà),就讓我們先從最簡(jiǎn)單的開(kāi)始。
  • 填充:在這里我們選擇pkcs標(biāo)準(zhǔn)的pkcs7padding。
  • 數(shù)據(jù)塊:我們選擇128位,因?yàn)閖ava端解密算法目前只支持AES128,所以我們先從128位開(kāi)始。
  • 密鑰:因?yàn)槲覀兦懊孢x擇了128位的數(shù)據(jù)塊,所以這里我們用128 / 8 = 16個(gè)字節(jié)來(lái)處理,我們先簡(jiǎn)單地填入16個(gè)0,其實(shí)你也可以填寫(xiě)任意字符,比如abcdefg1234567ab或者其它,只要是16個(gè)字節(jié)即可。理論上來(lái)說(shuō),不是16個(gè)字節(jié)也可以用來(lái)當(dāng)密鑰,優(yōu)秀的算法會(huì)自動(dòng)補(bǔ)齊,但是為了簡(jiǎn)單起見(jiàn),我們先填入16個(gè)0。
  • 偏移量:置空。因?yàn)槭荅CB模式,不需要iv偏移量。
  • 輸出:我們選擇base64編碼方式。
  • 字符集:這里因?yàn)槲覀冎患用苡⑽淖帜负桶⒗當(dāng)?shù)字,所以選擇utf-8和gb2312都是一樣的。

好了,現(xiàn)在我們知道按照以上選項(xiàng)設(shè)置好之后的代碼如果加密123456的話(huà),應(yīng)該輸出DoxDHHOjfol/2WxpaXAXgQ==,如果不是這個(gè)結(jié)果,那就是加密端的問(wèn)題。

AES-ECB

1. AES-ECB的Javascript加密

為了完成AES加密,我們并不需要自己手寫(xiě)一個(gè)AES算法,不需要去重復(fù)造輪子。但如何選擇js的加密庫(kù)是個(gè)很有意思的挑戰(zhàn)。我們嘗試了很多方法,一開(kāi)始我們嘗試了aes-js這個(gè)庫(kù),但它不支持RSA算法,后來(lái)我們看到Web Crypto API這種瀏覽器自帶的加密庫(kù),原生支持AES和RSA,但它的RSA實(shí)現(xiàn)和Java不兼容,最終我們還是選擇了Forge這個(gè)庫(kù),它天生支持AES的各種子集,并且它的RSA也能和Java完美配合。

使用forge編寫(xiě)的js代碼實(shí)現(xiàn)AES-ECB加密的代碼就是下面這些:

  1. const cipher = forge.cipher.createCipher('AES-ECB', '這里是16字節(jié)密鑰'); 
  2. cipher.start(); 
  3. cipher.update(forge.util.createBuffer('這里是明文')); 
  4. cipher.finish(); 
  5. const result = forge.util.encode64(cipher.output.getBytes()) 

forge的AES缺省就是pkcs7padding,所以不用特別設(shè)置。運(yùn)行它之后你就會(huì)得到正確的加密結(jié)果。

2. AES-ECB的Java解密

接下來(lái)我們看看Java端的解密代碼該如何寫(xiě):

  1. try { 
  2.     Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); 
  3.     cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec("這里是16字節(jié)密鑰".getBytes(), "AES")); 
  4.     String plaintext = new String(cipher.doFinal(Base64.getDecoder().decode("這里是明文".getBytes())), "UTF-8"); 
  5.     System.out.println(plaintext); 
  6. } catch (Exception e) { 
  7.     System.out.println("解密出錯(cuò):" + e.toString()); 

注意這里我們用到的是PKCS5Padding,上面加密的時(shí)候不是用的是pkcs7padding嗎?怎么這里變成5了呢?

我們先來(lái)了解一下什么是pkcs。pkcs的全稱(chēng)是Public Key Cryptography Standards(公鑰加密標(biāo)準(zhǔn)),這是RSA實(shí)驗(yàn)室制定的一系列的公鑰密碼編譯標(biāo)準(zhǔn),比較著名的有pkcs1, pkcs5, pkcs7, pkcs8這四個(gè),它們分別管理的是不同的內(nèi)容。在這里我們只是用它來(lái)填充,所以我們只關(guān)注pkcs5和pkcs7就夠了。那么pkcs5和pkcs7有什么區(qū)別呢?其實(shí)在填充方面它們兩個(gè)的算法是一樣的,pkcs5是pkcs7的一個(gè)子集,區(qū)別在于pkcs5是8字節(jié)固定的,而pkcs7可以是1到255之間的任意字節(jié)。但用在AES算法上,因?yàn)锳ES標(biāo)準(zhǔn)規(guī)定塊大小必須是16字節(jié)或者24字節(jié)或者32字節(jié),不可能用pkcs5的8字節(jié),所以AES算法只能用pkcs7填充。但是由于java早期工程師犯的一個(gè)命名上的錯(cuò)誤,他們把AES填充算法的名稱(chēng)設(shè)定為pkcs5,而實(shí)際實(shí)現(xiàn)中實(shí)現(xiàn)的是pkcs7,所以我們?cè)趈ava端開(kāi)發(fā)解密的時(shí)候需要使用pkcs5。

AES-CBC

談完了不安全的AES-ECB,我們來(lái)做一下相對(duì)安全一些的AES-CBC模式。

1. AES-CBC的Javascript加密

直接上代碼:

  1. const cipher = forge.cipher.createCipher('AES-CBC', '這里是16字節(jié)密鑰'); 
  2. cipher.start({ iv: '這里是16字節(jié)偏移量' }); 
  3. cipher.update(forge.util.createBuffer('這里是明文')); 
  4. cipher.finish(); 
  5. const result = forge.util.encode64(cipher.output.getBytes()); 

跟上面的AES-ECB差不多,唯一區(qū)別只是在start函數(shù)里定義了一個(gè)iv。

2. AES-CBC的Java解密

下面是Java代碼:

  1. try { 
  2.     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
  3.     cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec("這里是16字節(jié)密鑰".getBytes(), "AES"), new IvParameterSpec("這里是16字節(jié)偏移量".getBytes())); 
  4.     String plaintext = new String(cipher.doFinal(Base64.getDecoder().decode("這里是明文".getBytes())), "UTF-8"); 
  5.     System.out.println(plaintext); 
  6. } catch (Exception e) { 
  7.     System.out.println("解密出錯(cuò):" + e.toString()); 

也是同樣,跟上面用AES-ECB時(shí)的模式幾乎一模一樣,只是增加了一個(gè)IvParameterSpec,用來(lái)生成iv,在cipher.init里面增加了一個(gè)iv參數(shù),除此之外完全相同,就這樣我們就已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的CBC模式。

RSA

但是以上兩種做法都明顯是非常不安全的,因?yàn)槲覀儼鸭用苡玫拿荑€和iv參數(shù)都直接暴露在了前端,為此我們需要一種更加安全的加密方法——RSA。因?yàn)镽SA是非對(duì)稱(chēng)加密,即使我們把加密用的公鑰完全暴露在前端也不必?fù)?dān)心,別人即使截獲了我們的密文,但因?yàn)樗麄儧](méi)有解密密鑰,是無(wú)法解出我們的明文的。

1. 生成密鑰對(duì)

要用RSA加密,首先我們需要生成一個(gè)公鑰和一個(gè)私鑰,我們可以直接執(zhí)行命令ssh-keygen。它會(huì)問(wèn)我們密鑰文件保存的文件夾,注意一定要單獨(dú)找一個(gè)文件夾存放,不要放在缺省文件夾下,否則你日常使用的ssh公鑰和私鑰就都被覆蓋了。

得到公鑰文件之后,由于這個(gè)公鑰文件是rfc4716格式的,而我們的forge庫(kù)要求一個(gè)pkcs1格式的公鑰,所以這里我們需要把它轉(zhuǎn)換成pem格式(也就是pkcs1格式):

  1. ssh-keygen -f 公鑰文件名 -m pem -e 

2. RSA的Javascript加密

得到pem格式的公鑰之后,我們來(lái)看一下js的代碼:

  1. forge.util.encode64(forge.pki.publicKeyFromPem('-----BEGIN RSA PUBLIC KEY-----MIIBCfdsafasfasfafsdaafdsaAB-----END RSA PUBLIC KEY-----').encrypt('這里是明文', 'RSA-OAEP', { md: forge.md.sha256.create(), mgf1: { md: forge.md.sha1.create() } }); 

一句話(huà)就完成整個(gè)加密過(guò)程了,這就是forge的強(qiáng)大之處。

3. RSA的Java解密

接下來(lái)我們看解密。

對(duì)于私鑰,因?yàn)镴ava只支持PKCS8,而我們用ssh-keygen生成的私鑰是pkcs1的,所以還需要用以下命令把pkcs1的私鑰轉(zhuǎn)換為pkcs8的私鑰:

  1. openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in 私鑰文件名 -out 導(dǎo)出文件名 

得到pkcs8格式的私鑰之后,我們把這個(gè)文件的頭和尾去掉,然后放入以下Java代碼:

  1. try { 
  2.     Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); 
  3.     cipher.init(Cipher.DECRYPT_MODE, KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode("這里是私鑰")))); 
  4.     String plaintext = new String(cipher.doFinal(Base64.getDecoder().decode("這里是密文".getBytes())), "UTF-8"); 
  5.     System.out.println(plaintext); 
  6. } catch (Exception e) { 
  7.     System.out.println("解密出錯(cuò):" + e.toString()); 

和上面的AES解密類(lèi)似,只是增加了KeyFactory讀取PKCS8格式私鑰的部分,這樣我們就完成了Java端的RSA解密。

以上我們用最簡(jiǎn)單的方式實(shí)現(xiàn)了js端加密,java端解密的過(guò)程,感興趣的朋友可以在這里下載完整的代碼親自驗(yàn)證一下:

https://github.com/fengerzh/encdec

責(zé)任編輯:趙寧寧 來(lái)源: 日新亭
相關(guān)推薦

2011-05-20 10:39:43

oracle

2018-02-05 15:30:01

MariaDB服務(wù)器主從復(fù)制

2010-12-28 16:49:05

2010-01-06 16:40:30

cisco交換機(jī)vla

2009-08-26 14:25:46

C#消息

2012-03-01 22:37:02

Linux入門(mén)

2023-03-23 08:49:39

負(fù)載均衡服務(wù)器

2009-08-07 03:47:00

2011-05-24 13:47:25

程序員

2020-02-19 08:48:04

Java內(nèi)存模型CPU

2009-10-30 14:43:24

寬帶接入網(wǎng)

2010-12-28 16:38:16

Windows SerWindows 部署服

2022-04-21 14:03:54

開(kāi)發(fā)API生命周期

2010-12-31 15:28:41

Windows 7

2024-07-04 08:00:00

2022-03-31 06:23:43

自動(dòng)化響應(yīng)網(wǎng)絡(luò)安全

2022-04-29 11:27:26

循序漸進(jìn)!開(kāi)展零信任

2020-11-23 11:09:18

大數(shù)據(jù)教育云計(jì)算

2020-07-17 10:37:08

云計(jì)算安全IT

2013-12-03 09:57:15

下一代互聯(lián)網(wǎng)IPv6遷移
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)