apache shiro_Apache Shiro第3部分–密码学
apache shiro
除了保護網頁和管理訪問權限外, Apache Shiro還執行基本的加密任務。 該框架能夠:- 加密和解密數據,
- 哈希數據,
- 生成隨機數。
Shiro沒有實現任何加密算法。 所有計算都委托給Java密碼學擴展(JCE)API。 使用Shiro代替Java中已經存在的主要好處是易于使用和安全的默認值。 Shiro加密模塊以更高的抽象級別編寫,默認情況下實現所有已知的最佳實踐。
這是致力于Apache Shiro的系列文章的第三部分。 第一部分顯示了如何保護Web應用程序的安全以及如何添加登錄/注銷功能。 第二部分介紹了如何在數據庫中存儲用戶帳戶,以及如何為用戶提供通過PGP證書進行身份驗證的選項。
這篇文章從Shiro和JCE的簡短概述開始,并繼續介紹一些有用的轉換實用程序 。 以下各章介紹了隨機數的生成 , 散列以及如何加密和解密數據。 最后一章介紹了如何自定義密碼以及如何創建新密碼。
總覽
Shiro加密模塊位于org.apache.shiro.crypto包中。 它沒有手冊,但是幸運的是,所有加密類都是Javadoc繁重的。 Javadoc包含將用手動編寫的所有內容。
Shiro在很大程度上依賴于Java密碼學擴展。 您無需了解JCE即可使用Shiro。 但是,您需要JCE基礎知識來對其進行自定義或添加新功能。 如果您對JCE不感興趣,請跳到下一章。
JCE是一組高度可定制的API及其默認實現。 它是基于提供程序的。 如果默認實現沒有您需要的實現,則可以輕松安裝新的提供程序。
每個密碼,密碼選項,哈希算法或任何其他JCE功能都有一個名稱。 JCE為算法和算法模式定義了兩組 標準名稱 。 這些可用于任何JDK。 任何提供程序(例如Bouncy Castle )都可以使用新算法和選項自由擴展名稱集。
名稱由所謂的轉換字符串組成,用于查找所需的對象。 例如, Cipher.getInstance('DES/ECB/PKCS5Padding')在ECB模式下以PKCS#5填充返回DES密碼。 返回的密碼通常需要進一步的初始化,可能不使用安全的默認值,也不是線程安全的。
Apache Shiro組成轉換字符串,配置獲取的對象并為其添加線程安全性。 最重要的是,它具有易于使用的API,并添加了無論如何都應實施的更高級別的最佳實踐。
編碼,解碼和ByteSource
加密包對字節數組( byte[] )進行加密,解密和散列。 如果需要對字符串進行加密或哈希處理,則必須先將其轉換為字節數組。 相反,如果需要將散列或加密的值存儲在文本文件或字符串數??據庫列中,則必須將其轉換為字符串。
文本到字節數組
靜態類CodecSupport能夠將文本轉換為字節數組并返回。 方法byte[] toBytes(String source)將字符串轉換為字節數組,方法String toString(byte[] bytes)將其轉換回來。
例
使用編解碼器支持在文本和字節數組之間進行轉換 :
@Testpublic void textToByteArray() {String encodeMe = 'Hello, I'm a text.';byte[] bytes = CodecSupport.toBytes(encodeMe);String decoded = CodecSupport.toString(bytes);assertEquals(encodeMe, decoded);}編碼和解碼字節數組
從字節數組到字符串的轉換稱為編碼。 反向過程稱為解碼。 Shiro提供了兩種不同的算法:
- 在Base64類中實現的Base64 ,
- 在Hex類中實現的Hex 。
這兩個類都是靜態的,并且都具有encodeToString和decode實用程序方法。
例子
將隨機數組編碼為其十六進制表示形式,對其進行解碼并驗證結果:
@Testpublic void testStaticHexadecimal() {byte[] encodeMe = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};String hexadecimal = Hex.encodeToString(encodeMe);assertEquals('020406080a0c0e101214', hexadecimal);byte[] decoded = Hex.decode(hexadecimal);assertArrayEquals(encodeMe, decoded);}將隨機數組編碼為其Byte64表示形式 ,對其進行解碼并驗證結果:
@Testpublic void testStaticBase64() {byte[] encodeMe = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};String base64 = Base64.encodeToString(encodeMe);assertEquals('AgQGCAoMDhASFA==', base64);byte[] decoded = Base64.decode(base64);assertArrayEquals(encodeMe, decoded);}字節源
加密程序包通常返回ByteSource接口的實例,而不是字節數組。 它的實現SimpleByteSource是字節數組的簡單包裝,提供了其他可用的編碼方法:
- String toHex() –返回十六進制字節數組表示形式,
- String toBase64() –返回Base64字節數組表示形式,
- byte[] getBytes() –返回包裝的字節數組。
例子
該測試使用ByteSource將數組編碼成其十六進制表示形式。 然后解碼并驗證結果:
@Testpublic void testByteSourceHexadecimal() {byte[] encodeMe = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};ByteSource byteSource = ByteSource.Util.bytes(encodeMe);String hexadecimal = byteSource.toHex();assertEquals('020406080a0c0e101214', hexadecimal);byte[] decoded = Hex.decode(hexadecimal);assertArrayEquals(encodeMe, decoded);}使用Bytesource將數組編碼成其Base64表示形式。 對其進行解碼并驗證結果:
@Testpublic void testByteSourceBase64() {byte[] encodeMe = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};ByteSource byteSource = ByteSource.Util.bytes(encodeMe);String base64 = byteSource.toBase64();assertEquals('AgQGCAoMDhASFA==', base64);byte[] decoded = Base64.decode(base64);assertArrayEquals(encodeMe, decoded);}隨機數發生器
隨機數生成器由RandomNumberGenerator接口及其默認實現SecureRandomNumberGenerator組成 。
該接口非常簡單,只有兩種方法:
- ByteSource nextBytes() –生成一個隨機的固定長度的字節源,
- ByteSource nextBytes(int numBytes) –生成具有指定長度的隨機字節源。
默認實現實現了這兩種方法,并提供了一些其他配置:
- setSeed(byte[] bytes) –自定義種子配置,
- setDefaultNextBytesSize(int defaultNextBytesSize) – nextBytes()輸出的長度。
種子是一個數字(實際上是字節數組),用于初始化隨機數生成器。 它允許您生成“可預測的隨機數”。 用同一種子初始化的同一隨機生成器的兩個實例始終生成相同的隨機數序列。 它對于調試很有用,但要非常小心。
如果可以,請不要為加密指定自定義種子。 使用默認值。 除非您真的知道自己在做什么,否則攻擊者可能會猜出定制的那個。 這將超過隨機數的所有安全性目的。
在幕后:SecureRandomNumberGenerator將隨機數生成委托給JCE SecureRandom實現。
例子
第一個示例創建兩個隨機數生成器,并驗證它們是否生成兩個不同的事物:
@Testpublic void testRandomWithoutSeed() {//create random generatorsRandomNumberGenerator firstGenerator = new SecureRandomNumberGenerator();RandomNumberGenerator secondGenerator = new SecureRandomNumberGenerator();//generate random bytesByteSource firstRandomBytes = firstGenerator.nextBytes();ByteSource secondRandomBytes = secondGenerator.nextBytes();//compare random bytesassertByteSourcesNotSame(firstRandomBytes, secondRandomBytes);}第二個示例創建兩個隨機數生成器,使用相同的種子對其進行初始化,并檢查它們是否生成相同的預期20字節長的隨機數組:
@Testpublic void testRandomWithSeed() {byte[] seed = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};//create and initialize first random generatorSecureRandomNumberGenerator firstGenerator = new SecureRandomNumberGenerator();firstGenerator.setSeed(seed);firstGenerator.setDefaultNextBytesSize(20);//create and initialize second random generatorSecureRandomNumberGenerator secondGenerator = new SecureRandomNumberGenerator();secondGenerator.setSeed(seed);secondGenerator.setDefaultNextBytesSize(20);//generate random bytesByteSource firstRandomBytes = firstGenerator.nextBytes();ByteSource secondRandomBytes = secondGenerator.nextBytes();//compare random arraysassertByteSourcesEquals(firstRandomBytes, secondRandomBytes);//following nextBytes are also the sameByteSource firstNext = firstGenerator.nextBytes();ByteSource secondNext = secondGenerator.nextBytes();//compare random arraysassertByteSourcesEquals(firstRandomBytes, secondRandomBytes);//compare against expected valuesbyte[] expectedRandom = {-116, -31, 67, 27, 13, -26, -38, 96, 122, 31, -67, 73, -52, -4, -22, 26, 18, 22, -124, -24};assertArrayEquals(expectedRandom, firstNext.getBytes());}散列
哈希 函數將任意長數據作為輸入,并將其轉換為較小的固定長度數據。 哈希函數的結果稱為哈希。 散列是一種操作方式。 無法將哈希轉換回原始數據。
要記住的最重要的事情是:始終存儲密碼哈希而不是密碼本身。 永遠不要直接存儲它。
Shiro提供了兩個與哈希相關的接口,均支持安全密碼哈希所必需的兩個概念:鹽析和哈希迭代:
- Hash -表示哈希算法。
- Hasher -用此來哈希密碼。
鹽是散列前與密碼連接的隨機數組。 它通常與密碼一起存儲。 沒有鹽,相同的密碼將具有相同的哈希。 這將使密碼破解變得容易得多。
指定許多哈希迭代,以減慢哈希操作的速度。 操作越慢,破解存儲密碼的難度就越大。 使用很多迭代。
雜湊
哈希接口實現計算哈希函數。 四郎器具六個標準散列函數: MD2 , 被Md5 , SHA1 , SHA256 , SHA3??84和SHA512 。
每個哈希實現都從ByteSource擴展。 構造函數獲取輸入數據,鹽和所需的迭代次數。 鹽和迭代數是可選的。
ByteSource接口方法返回:
- byte[] getBytes() –哈希,
- String toBase64() – Base64表示形式的哈希,
- String toHex() -以十六進制表示形式的哈希值。
以下代碼可計算出不含鹽的“ Hello Md5”??文本的Md5哈希值:
@Testpublic void testMd5Hash() {Hash hash = new Md5Hash('Hello Md5');byte[] expectedHash = {-7, 64, 38, 26, 91, 99, 33, 9, 37, 50, -22, -112, -99, 57, 115, -64};assertArrayEquals(expectedHash, hash.getBytes());assertEquals('f940261a5b6321092532ea909d3973c0', hash.toHex());assertEquals('+UAmGltjIQklMuqQnTlzwA==', hash.toBase64());print(hash, 'Md5 with no salt iterations of 'Hello Md5': ');}下一個代碼段用salt計算Sha256的10次迭代:
@Testpublic void testIterationsSha256Hash() {byte[] salt = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};Hash hash = new Sha256Hash('Hello Sha256', salt, 10);byte[] expectedHash = {24, 4, -97, -61, 70, 28, -29, 85, 110, 0, -107, -8, -12, -93, -121, 99, -5, 23, 60, 46, -23, 92, 67, -51, 65, 95, 84, 87, 49, -35, -78, -115};String expectedHex = '18049fc3461ce3556e0095f8f4a38763fb173c2ee95c43cd415f545731ddb28d';String expectedBase64 = 'GASfw0Yc41VuAJX49KOHY/sXPC7pXEPNQV9UVzHdso0=';assertArrayEquals(expectedHash, hash.getBytes());assertEquals(expectedHex, hash.toHex());assertEquals(expectedBase64, hash.toBase64());print(hash, 'Sha256 with salt and 10 iterations of 'Hello Sha256': ');}比較由框架和客戶端代碼計算出的迭代 :
@Testpublic void testIterationsDemo() {byte[] salt = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};//iterations computed by the framework Hash shiroIteratedHash = new Sha256Hash('Hello Sha256', salt, 10);//iterations computed by the client code Hash clientIteratedHash = new Sha256Hash('Hello Sha256', salt);for (int i = 1; i < 10; i++) {clientIteratedHash = new Sha256Hash(clientIteratedHash.getBytes());}//compare resultsassertByteSourcesEquals(shiroIteratedHash, clientIteratedHash);}在幕后:所有具體的哈希類都從SimpleHash擴展,后者將哈希計算委托給JCE MessageDigest實現。 如果您希望使用其他哈希函數擴展Shiro,請直接對其進行實例化。 構造函數將JCE消息摘要(哈希)算法名稱作為參數。
哈舍爾
Hasher在哈希函數之上工作,并實現了與鹽腌相關的最佳實踐。 該接口只有一種方法:
- HashResponse computeHash(HashRequest request)
哈希請求提供了要哈希的字節源和可選的鹽。 哈希響應返回哈希和鹽。 響應鹽不必與提供的鹽相同。 更重要的是,它可能不是用于哈希操作的全部鹽。
任何哈希器實現均可自由生成自己的隨機鹽。 僅當請求包含null鹽時,默認實現才執行此操作。 另外,用過的鹽可以由“堿鹽”和“公共鹽”組成。 哈希響應中返回“公共鹽”。
要了解為什么要用這種方法,必須記得鹽通常與密碼一起存儲。 具有數據庫訪問權限的攻擊者將擁有暴力攻擊所需的所有信息。
因此,“公共鹽”與密碼存儲在同一位置,“基本鹽”存儲在其他位置。 然后,攻擊者需要訪問兩個不同的位置。
默認哈希器是可配置的。 您可以指定基本鹽,要使用的迭代次數和哈希算法。 使用任何Shiro哈希實現中的哈希算法名稱。 它還總是從哈希請求中返回公共鹽。 觀看演示 :
@Testpublic void fullyConfiguredHasher() {ByteSource originalPassword = ByteSource.Util.bytes('Secret');byte[] baseSalt = {1, 1, 1, 2, 2, 2, 3, 3, 3};int iterations = 10;DefaultHasher hasher = new DefaultHasher();hasher.setBaseSalt(baseSalt);hasher.setHashIterations(iterations);hasher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME);//custom public saltbyte[] publicSalt = {1, 3, 5, 7, 9};ByteSource salt = ByteSource.Util.bytes(publicSalt);//use hasher to compute password hashHashRequest request = new SimpleHashRequest(originalPassword, salt);HashResponse response = hasher.computeHash(request);byte[] expectedHash = {55, 9, -41, -9, 82, -24, 101, 54, 116, 16, 2, 68, -89, 56, -41, 107, -33, -66, -23, 43, 63, -61, 6, 115, 74, 96, 10, -56, -38, -83, -17, 57};assertArrayEquals(expectedHash, response.getHash().getBytes());}如果您需要比較密碼或數據校驗和,請在同一哈希表中提供一個“公用鹽”。 它將重現哈希操作。 該示例使用Shiro DefaultHasher實現:
@Testpublic void hasherDemo() {ByteSource originalPassword = ByteSource.Util.bytes('Secret');ByteSource suppliedPassword = originalPassword;Hasher hasher = new DefaultHasher();//use hasher to compute password hashHashRequest originalRequest = new SimpleHashRequest(originalPassword);HashResponse originalResponse = hasher.computeHash(originalRequest);//Use salt from originalResponse to compare stored password with user supplied password. We assume that user supplied correct password.HashRequest suppliedRequest = new SimpleHashRequest(suppliedPassword, originalResponse.getSalt());HashResponse suppliedResponse = hasher.computeHash(suppliedRequest);assertEquals(originalResponse.getHash(), suppliedResponse.getHash());//important: the same request hashed twice may lead to different results HashResponse anotherResponse = hasher.computeHash(originalRequest);assertNotSame(originalResponse.getHash(), anotherResponse.getHash());}注意:由于上面示例中提供的公共鹽為null ,因此默認的hashher會生成新的隨機公共鹽。
加密/解密
密碼將數據加密為沒有密鑰的不可讀的密文。 密碼分為兩組:對稱和不對稱。 對稱密碼使用相同的密鑰進行加密和解密。 非對稱密碼使用兩個不同的密鑰,一個用于加密,另一個用于解密。
Apache Shiro包含兩個對稱密碼: AES和Blowfish 。 兩者都是無狀態的,因此是線程安全的。 不支持非對稱密碼。
兩種密碼均能夠生成隨機加密密鑰,并且均實現CipherService接口。 該接口定義了兩種加密和兩種解密方法。 第一組用于字節數組的加密/解密:
- ByteSource encrypt(byte[] raw, byte[] encryptionKey) ,
- ByteSource decrypt(byte[] encrypted, byte[] decryptionKey) 。
第二組加密/解密流:
- encrypt(InputStream in, OutputStream out, byte[] encryptionKey) ,
- decrypt(InputStream in, OutputStream out, byte[] decryptionKey) 。
下一個代碼段生成新密鑰,使用AES密碼對秘密消息進行加密,對其進行解密,并將原始消息與解密結果進行比較:
@Testpublic void encryptStringMessage() {String secret = 'Tell nobody!';AesCipherService cipher = new AesCipherService();//generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();//encrypt the secretbyte[] secretBytes = CodecSupport.toBytes(secret);ByteSource encrypted = cipher.encrypt(secretBytes, keyBytes);//decrypt the secretbyte[] encryptedBytes = encrypted.getBytes();ByteSource decrypted = cipher.decrypt(encryptedBytes, keyBytes);String secret2 = CodecSupport.toString(decrypted.getBytes());//verify correctnessassertEquals(secret, secret2);}另一個片段顯示了如何使用Blowfish加密/解密流。 Shiro密碼不會關閉或刷新輸入流或輸出流。 您必須自己做:
@Testpublic void encryptStream() {InputStream secret = openSecretInputStream();BlowfishCipherService cipher = new BlowfishCipherService();// generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();// encrypt the secretOutputStream encrypted = openSecretOutputStream();try {cipher.encrypt(secret, encrypted, keyBytes);} finally {// The cipher does not flush neither close streams.closeStreams(secret, encrypted);}// decrypt the secretInputStream encryptedInput = convertToInputStream(encrypted);OutputStream decrypted = openSecretOutputStream();try {cipher.decrypt(encryptedInput, decrypted, keyBytes);} finally {// The cipher does not flush neither close streams.closeStreams(secret, encrypted);}// verify correctnessassertStreamsEquals(secret, decrypted);}如果使用相同的密鑰兩次加密相同的文本 ,則會得到兩個不同的加密文本:
@Testpublic void unpredictableEncryptionProof() {String secret = 'Tell nobody!';AesCipherService cipher = new AesCipherService();// generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();// encrypt two timesbyte[] secretBytes = CodecSupport.toBytes(secret);ByteSource encrypted1 = cipher.encrypt(secretBytes, keyBytes);ByteSource encrypted2 = cipher.encrypt(secretBytes, keyBytes);// verify correctnessassertArrayNotSame(encrypted1.getBytes(), encrypted2.getBytes());}前面的兩個示例都使用Key generateNewKey()方法生成密鑰。 使用方法setKeySize(int keySize)覆蓋默認密鑰大小(128位)。 或者,方法Key generateNewKey(int keyBitSize)的方法的keyBitSize參數以位為單位指定密鑰大小。
有些密碼僅支持某些密鑰大小。 例如 ,AES僅支持128、192和256位日志密鑰:
@Test(expected=RuntimeException.class)public void aesWrongKeySize() {AesCipherService cipher = new AesCipherService();//The call throws an exception. Aes supports only keys of 128, 192, and 256 bits.cipher.generateNewKey(200);}@Testpublic void aesGoodKeySize() {AesCipherService cipher = new AesCipherService();//aes supports only keys of 128, 192, and 256 bitscipher.generateNewKey(128);cipher.generateNewKey(192);cipher.generateNewKey(256);}就基礎而言,就是這樣。 您不需要更多的內容來加密和解密應用程序中的敏感數據。
更新:我在這里過于樂觀。 了解更多信息總是很有用的,尤其是在處理敏感數據時。 此方法大部分是但不是完全安全的。 問題和解決方案都在我的另一篇文章中進行了介紹。
加密/解密-高級
上一章介紹了如何加密和解密某些數據。 本章將進一步介紹Shiro加密的工作原理以及如何對其進行自定義。 它還顯示了如果標準的兩個密碼不適合您,那么如何輕松添加新密碼。
初始化向量
初始化向量是在加密期間使用的隨機生成的字節數組。 使用初始化向量的密碼很難預測,因此對于攻擊者來說很難解密。
Shiro自動生成初始化向量并將其用于加密數據。 然后,將向量與加密的數據連接起來并返回給客戶端代碼。 您可以通過在密碼上調用setGenerateInitializationVectors(false)將其關閉。 該方法在JcaCipherService類上定義。 這兩個默認加密類都對其進行了擴展。
初始化向量大小是特定于加密算法的。 如果默認大小(128位)不起作用,請使用setInitializationVectorSize方法對其進行自定義。
隨機發生器
關閉初始化向量不一定意味著密碼變得可預測。 河豚和AES都具有隨機性。
以下示例關閉了初始化向量,但是加密的文本仍然不同:
@Testpublic void unpredictableEncryptionNoIVProof() {String secret = 'Tell nobody!';AesCipherService cipher = new AesCipherService();cipher.setGenerateInitializationVectors(false);// generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();// encrypt two timesbyte[] secretBytes = CodecSupport.toBytes(secret);ByteSource encrypted1 = cipher.encrypt(secretBytes, keyBytes);ByteSource encrypted2 = cipher.encrypt(secretBytes, keyBytes);// verify correctnessassertArrayNotSame(encrypted1.getBytes(), encrypted2.getBytes());}可以自定義或關閉隨機性。 但是,永遠不要在生產代碼中這樣做。 隨機性是安全數據加密的絕對必要條件。
兩種Shiro加密算法都從JcaCipherService類擴展。 該類具有setSecureRandom(SecureRandom secureRandom)方法。 安全隨機數是標準的Java JCE隨機數生成器。 擴展它以創建自己的實現,并將其傳遞給密碼。
我們的SecureRandom ConstantSecureRandom實現始終返回零。 我們將其提供給密碼,然后關閉初始化向量以創建不安全的可預測加密 :
@Testpublic void predictableEncryption() {String secret = 'Tell nobody!';AesCipherService cipher = new AesCipherService();cipher.setSecureRandom(new ConstantSecureRandom());cipher.setGenerateInitializationVectors(false);// define the keybyte[] keyBytes = {5, -112, 36, 113, 80, -3, -114, 77, 38, 127, -1, -75, 65, -102, -13, -47};// encrypt first timebyte[] secretBytes = CodecSupport.toBytes(secret);ByteSource encrypted = cipher.encrypt(secretBytes, keyBytes);// verify correctness, the result is always the samebyte[] expectedBytes = {76, 69, -49, -110, -121, 97, -125, -111, -11, -61, 61, 11, -40, 26, -68, -58};assertArrayEquals(expectedBytes, encrypted.getBytes());}持續安全的隨機實現是漫長而無趣的。 在Github上可用 。
自定義密碼
開箱即用Shiro僅提供Blowfish和AES加密方法。 該框架沒有實現自己的算法。 而是將加密委托給JCE類。
Shiro僅提供安全的默認設置和更簡單的API。 這種設計使得可以使用任何JCE分組密碼來擴展Shiro。
塊密碼對每個塊的消息進行加密。 所有塊具有相等的固定大小。 如果最后一個塊太短,則添加填充以使其與其他所有塊相同。 每個塊被加密并與先前加密的塊組合。
因此,您必須配置:
- 加密方法
- 塊大小
- 填充
- 如何結合塊 。
加密方式
自定義密碼擴展了DefaultBlockCipherService類。 該類只有一個帶有一個參數的構造函數:算法名稱。 您可以提供任何與JCE兼容的算法名稱 。
例如,這是Shiro AES密碼的源代碼:
public class AesCipherService extends DefaultBlockCipherService {private static final String ALGORITHM_NAME = 'AES';public AesCipherService() {super(ALGORITHM_NAME);}}AES不需要指定其他加密參數(塊大小,填充,加密方法)。 對于AES,默認值足夠好。
塊大小
默認的塊密碼服務有兩種方法可以自定義塊大小。 setBlockSize(int blockSize)方法僅適用于字節數組的編碼和解碼。 setStreamingBlockSize(int streamingBlockSize)方法僅適用于流編碼和解碼。
值0表示將使用默認算法特定的塊大小。 這是默認值。
分組密碼塊大小是特定于算法的。 選定的加密算法可能不適用于任意塊大小 :
@Test(expected=CryptoException.class)public void aesWrongBlockSize() {String secret = 'Tell nobody!';AesCipherService cipher = new AesCipherService();// set wrong block size cipher.setBlockSize(200);// generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();// encrypt the secretbyte[] secretBytes = CodecSupport.toBytes(secret);cipher.encrypt(secretBytes, keyBytes);}填充
使用方法setPaddingScheme(PaddingScheme paddingScheme)指定字節數組加密和解密填充。 setStreamingPaddingScheme( PaddingScheme paddingScheme)指定流加密和解密填充。
枚舉PaddingScheme代表所有典型的填充方案。 默認情況下,并非所有功能都可用,您可能必須安裝自定義JCE提供程序才能使用它們。
值null表示將使用默認算法特定的填充。 這是默認值。
如果需要PaddingScheme枚舉中未包含的填充,請使用setPaddingSchemeName或setStreamingPaddingSchemeName方法。 這些方法采用帶有填充方案名稱作為參數的字符串。 它們比上述類型的類型安全性差,但更靈活。
填充非常特定于算法。 選定的加密算法可能不適用于任意填充 :
@Test(expected=CryptoException.class)public void aesWrongPadding() {String secret = 'Tell nobody!';BlowfishCipherService cipher = new BlowfishCipherService();// set wrong block size cipher.setPaddingScheme(PaddingScheme.PKCS1);// generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();// encrypt the secretbyte[] secretBytes = CodecSupport.toBytes(secret);cipher.encrypt(secretBytes, keyBytes);}操作模式
操作模式指定塊如何鏈接(組合)在一起。 與填充方案一樣,您可以使用OperationMode枚舉或字符串來提供它們。
注意,并非每種操作模式都可用。 此外,他們并非天生平等。 某些鏈接模式不如其他鏈接模式安全。 默認的密碼反饋操作模式既安全又可在所有JDK環境中使用。
設置字節數組加密和解密操作模式的方法:
- setMode(OperationMode mode)
- setModeName(String modeName)
設置流加密和解密操作模式的方法:
- setStreamingMode(OperationMode mode)
- setStreamingModeName(String modeName)
練習–解密Openssl
假設應用程序發送使用Linux openssl命令加密的數據。 我們知道用于加密數據的密鑰和命令的十六進制表示形式:
- 密鑰: B9FAB84B65870109A6E8707BC95151C245BF18204C028A6A 。
- 命令: openssl des3 -base64 -p -K <secret key> -iv <initialization vector> 。
每個消息都包含初始化向量的十六進制表示形式和base64編碼的加密消息。
樣本消息:
- 初始化向量: F758CEEB7CA7E188 。
- 消息: GmfvxhbYJbVFT8Ad1Xc+Gh38OBmhzXOV 。
使用OpenSSL生成樣本
示例消息已使用以下命令加密:
#encrypt 'yeahh, that worked!' echo yeahh, that worked! | openssl des3 -base64 -p -K B9FAB84B65870109A6E8707BC95151C245BF18204C028A6A -iv F758CEEB7CA7E188使用OpenSSL選項-P可以生成密鑰或隨機初始向量。
解
首先,我們必須找出算法名稱,填充和操作模式。 幸運的是,這三個文件都可以在OpenSSL 文檔中找到 。 Des3 是 CBC模式下三重DES加密算法的別名 ,而OpenSSL 使用 PKCS#5填充。
密碼塊鏈接 (CBC)需要與塊大小相同大小的初始化向量。 三重DES需要64位長的塊。 Java JCE對Triple DES 使用“ DESede”算法名稱。
我們的自定義密碼擴展并配置了DefaultBlockCipherService :
public class OpensslDes3CipherService extends DefaultBlockCipherService {public OpensslDes3CipherService() {super('DESede');setMode(OperationMode.CBC);setPaddingScheme(PaddingScheme.PKCS5);setInitializationVectorSize(64);}}Shiro密碼decrypt方法需要兩個輸入字節數組,密文和密鑰。 密文應同時包含初始化向量和加密密文。 因此,在嘗試解密消息之前,我們必須將它們組合在一起。 該方法combine 兩個數組合并為一個:
private byte[] combine(byte[] iniVector, byte[] ciphertext) {byte[] ivCiphertext = new byte[iniVector.length + ciphertext.length];System.arraycopy(iniVector, 0, ivCiphertext, 0, iniVector.length);System.arraycopy(ciphertext, 0, ivCiphertext, iniVector.length, ciphertext.length);return ivCiphertext;}實際的解密通常像下面這樣 :
@Testpublic void opensslDes3Decryption() {String hexInitializationVector = 'F758CEEB7CA7E188';String base64Ciphertext = 'GmfvxhbYJbVFT8Ad1Xc+Gh38OBmhzXOV';String hexSecretKey = 'B9FAB84B65870109A6E8707BC95151C245BF18204C028A6A';//decode secret message and initialization vectorbyte[] iniVector = Hex.decode(hexInitializationVector);byte[] ciphertext = Base64.decode(base64Ciphertext);//combine initialization vector and ciphertext togetherbyte[] ivCiphertext = combine(iniVector, ciphertext);//decode secret keybyte[] keyBytes = Hex.decode(hexSecretKey);//initialize cipher and decrypt the messageOpensslDes3CipherService cipher = new OpensslDes3CipherService();ByteSource decrypted = cipher.decrypt(ivCiphertext, keyBytes);//verify resultString theMessage = CodecSupport.toString(decrypted.getBytes());assertEquals('yeahh, that worked!\n', theMessage);}結束
Apache Shiro教程的這一部分介紹了1.2版中可用的加密功能。 所有使用的示例都可以在Github上找到 。
參考: Apache Shiro第3部分–來自JCG合作伙伴 Maria Jurcovicova的“ 密碼”,來自This is Stuff博客。
翻譯自: https://www.javacodegeeks.com/2012/05/apache-shiro-part-3-cryptography.html
apache shiro
總結
以上是生活随笔為你收集整理的apache shiro_Apache Shiro第3部分–密码学的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 凯立德路径设置(凯立德配置文件)
- 下一篇: adf开发_ADF BC:创建绑定到业务