订阅号做微网站需要认证吗,wordpress 1.6.2漏洞,建设银行东航龙卡登录东航网站,微信社群营销推广方案上一次我写关于密码学的文章时 #xff0c;我概述了Apache Shiro加密API#xff0c;并展示了如何使用其两个对称密码。 我还写道#xff1a;“您不需要在应用程序中对敏感数据进行加密和解密。” 我了解了更多有关密码的知识#xff0c;发现您需要了解更多信息。 我写的内容… 上一次我写关于密码学的文章时 我概述了Apache Shiro加密API并展示了如何使用其两个对称密码。 我还写道“您不需要在应用程序中对敏感数据进行加密和解密。” 我了解了更多有关密码的知识发现您需要了解更多信息。 我写的内容在一定程度上是正确的但是除非您小心谨慎否则敏感数据可能并非对所有攻击者都是安全的。 开箱即用Shiro提供了Blowfish-CBC和AES-CBC加密方法我建议使用它们。 两者都旨在防止被动窃听攻击者并且擅长于此。 不幸的是真正的攻击者更加复杂可能会破坏基于他们的系统。 注意“可能”。 攻击者只有在被攻击系统至少与他配合时才能成功。 如果要使用这些密码则必须知道如何安全地编写系统。 当然另一种选择是使用更强的密码并完全避免该问题。 第一章中很少解释所需的理论术语和概念 。 第二章介绍了如何以更安全的方式加密数据。 然后我们描述了Blowfish-CBC和AES-CBC的工作方式并展示了对它们的两种可能的攻击 。 最后该帖子的末尾包含对其他资源的引用 理论 我们从一些理论概念开始。 如果您不想阅读它请直接转到解决方案的章节。 首先要描述的重要事情是被动攻击者和主动攻击者之间的区别。 然后我们解释什么是分组密码以及什么是经过身份验证的加密。 最后两个子章节列出了选定的易受攻击的密码和选定的安全密码。 主动与被动攻击者 仅窃听攻击者通常是被动的。 他能够读取加密的通信但无法对其进行修改或将新的密文发送给通信应用程序。 他无法影响交流他只会听。 他的被动性只有一个例外攻击者能够将未加密的信息提供给通信方之一并获得具有该确切信息的密文。 现实世界中的攻击者通常更活跃。 他们编写自己的消息并将其发送到通信应用程序。 然后该应用程序假定这些消息已加密因此它将尝试对其进行解密。 攻击者观察其反应返回的错误代码需要的响应时间等并进一步了解密码。 如果幸运的话他可能会使用获得的知识来破解密码或植入虚假信息。 分组密码 分组密码只能加密短消息。 例如AES只能加密16个字节长的消息而Blowfish只能加密8个字节长的消息。 较长的消息分为多个块。 每个块与先前加密的块组合然后传递到块密码。 块组合被称为操作模式并且有多种安全方式来实现。 本文讨论的两种主动攻击是对CBC操作模式的攻击。 分组密码本身是安全的。 认证加密 经过身份验证的加密将任何修改后的密文视为无效。 无法获取加密的数据对其进行修改并以有效的密文结尾。 此属性也称为密文完整性。 密码首先检查完整性然后以相同方式拒绝所有已修改的消息。 由于攻击者无法通过完整性检查因此他从向应用程序发送新消息中得不到任何好处。 身份验证和密文完整性通常但不总是由操作模式提供。 弱势密码 任何不提供密文完整性或未经过身份验证的加密的密码都可能会受到一些主动攻击。 使用哪个加密库都没有关系。 加密算法是在标准中定义的并且与所使用的库无关其含义相同。 换句话说如果可以在不知道秘密密钥的情况下创建有效的密文那么即使不知道也可能存在一些主动攻击。 这篇文章描述了两种攻击CBC的操作模式。 一旦了解了这些攻击以及被动和主动攻击者之间的区别您就应该能够对CFBCTROFB或其他未经身份验证的密码模式提出类似的攻击。 安全密码 经过身份验证的加密可以防止主动攻击者的攻击。 还提供身份验证的最常见操作模式是 GCM EAX CCM 用这些模式之一例如AES-EAX替换CBC您将拥有一个针对活动攻击者的安全密码。 当然针对主动攻击者的安全并不意味着该密码没有其他实际限制。 例如大多数密码只有在用户加密了太多数据后才更改密钥时才是安全的。 如果您认真对待数据加密则应该研究并了解这些限制。 使用Shiro进行身份验证的加密 本章介绍如何在Java中使用经过身份验证的加密。 对于那些不了解该理论的人 认证加密可以防止数据篡改。 没有密钥的任何人都不能修改加密的消息并且不可能对这种密码进行主动攻击。 Apache Shiro没有实现自己的加密算法。 它将所有工作委托给Java密码学扩展JCE每个Java运行时中都可用。 Shiro“仅”提供易于使用的API和安全的默认值。 因此我们必须做两件事 将经过身份验证的操作模式安装到Java密码扩展中 将Shiro与新的经过验证的操作模式集成。 安装经过身份验证的操作模式 Java密码术扩展是一个可扩展的API。 所有类和算法都是由提供程序创建的新提供程序可以随时安装到系统中。 提供者可以是 安装到Java运行时中并且可用于所有Java应用程序 与应用程序一起分发和初始化。 我们将展示如何将Bouncy Castle添加到项目中。 Bouncy Castle是根据MIT许可分发的提供商并且包含EAX和GCM操作模式。 Bouncy Castle分发了多个jar文件每个文件针对不同的Java版本进行了优化。 由于我们的演示项目使用Java 6因此我们必须使用bcprov-jdk16库。 将maven依赖项添加到pom.xml中 dependencygroupIdorg.bouncycastle/groupIdartifactIdbcprov-jdk16/artifactIdversion1.46/version
/dependency 库存在后您必须将其提供程序安装到java安全系统中。 在应用程序启动时运行以下方法 private BouncyCastleProvider installBouncyCastle() {BouncyCastleProvider provider new BouncyCastleProvider();Security.addProvider(provider);return provider;
} 现在已安装Bouncy Castle并且两种验证操作模式都可用。 与Shiro集成 Shiro加密软件包基本上是JCE上易于使用的外观。 它将JCE对象配置为使用安全默认值并为其添加线程安全性。 扩展DefaultBlockCipherService类以利用这些功能。 大多数配置都由该类完成但是我们仍然必须指定三件事 分组密码名称和参数 操作模式 填充。 块密码名称在构造函数参数中指定。 我们将使用AES因为它不需要其他配置。 GCM和CCM都不需要填充因此我们必须指定填充NONE 。 新密码服务 class GCMCipherService extends DefaultBlockCipherService {private static final String ALGORITHM_NAME AES;public GCMCipherService() {super(ALGORITHM_NAME);setMode(OperationMode.GCM);setPaddingScheme(PaddingScheme.NONE);}} 这就对了。 您可以将新的身份验证密码用作任何其他Shiro密码服务 。 测试用例 我们创建了一个简单的测试用例以证明完整性检查有效。 它加密消息并更改其密文的第三个字节。 如果密码提供了身份验证则尝试解密修改后的密文将导致运行时异常 Test
public void testGCMAuthentication() {String message secret message;GCMCipherService gcmCipher new GCMCipherService();assertIngetrityCheck(message, gcmCipher);
}private void assertIngetrityCheck(String message, DefaultBlockCipherService cipher) {byte[] key cipher.generateNewKey().getEncoded();byte[] messageBytes CodecSupport.toBytes(message);ByteSource encrypt cipher.encrypt(messageBytes, key);// change the ciphertextencrypt.getBytes()[3] 0;try {// it should be impossible to decrypt changed ciphertextcipher.decrypt(encrypt.getBytes(), key).getBytes();} catch (Exception ex) {return;}fail(It should not be possible to decrypt changed ciphertext.);
} 关于Java 7的注意事项 根据文档 Java 7支持两种经过身份验证的操作模式CCM和GCM。 从理论上讲您不需要第三方加密提供程序。 不幸的是Oracle无法在第一个JDK 7版本中提供这些模式的完整实现。 他们希望将其添加到更新版本中因此情况将来可能会发生变化。 Oracle错误数据库包含两个相关的 错误 。 Java 1.7.0_01仍然没有它们。 密码块链接CBC 在描述承诺的攻击之前我们需要做的最后一件事是密码块链接CBC操作模式。 这种操作模式足以抵御被动攻击者相当快速且易于实施。 不幸的是它也容易受到主动攻击。 基本 CBC用于使用分组密码对长消息进行加密。 分组密码只能加密短数据块因此它首先将消息拆分为短数据块。 第一个和最后一个块是特殊情况。 我们将在以下子章节中说明如何处理它们。 现在假设消息开头已被加密并且其第i个块m i与密文c i相对应。 分两个步骤对下一个消息块进行加密 用上一个块的密文对该块进行异或例如 m i ?c i-1 使用分组密码例如Blowfish(key, m i ?c i-1 ) 对结果进行加密。 示例假设该秘密消息有三个块我们正在尝试使用Blowfish-CBC对其进行加密。 第一块已经被加密其密文为1, 2, 3, 4, 5, 6, 7, 8 。 第二个块是字节数组1, 0, 1, 0, 1, 0, 1, 0 。 步骤1将第一个块密文与第二个块异或 1, 2, 3, 4, 5, 6, 7, 8 ? 1, 0, 1, 0, 1, 0, 1, 0 0, 2, 2, 4, 4, 6, 6, 8 步骤2使用河豚算法对结果进行加密 Blowfish(secret_key, {0, 2, 2, 4, 4, 6, 6, 8}) 第一块–初始化向量 第一个块没有要合并的前一个块。 因此我们将生成一个称为初始化向量的随机数据块。 初始化向量用作数据的第一块。 它与第一个消息块进行异或运算并使用块密码对结果进行加密。 初始化向量作为密文的第一个块未经加密地发送。 如果没有密文接收者将无法解密密文也没有理由对其保密。 示例假设该秘密消息有三个块我们正在尝试使用Blowfish-CBC对其进行加密。 第一个块是一个字节数组1, 1, 1, 1, 1, 1, 1, 1 。 步骤1产生随机初始化向量 1, 8, 2, 7, 3, 6, 4, 5 步骤2将第一个块与初始化向量进行异或 1, 8, 2, 7, 3, 6, 4, 5 ? 1, 1, 1, 1, 1, 1, 1, 1 0, 9, 3, 6, 2, 7, 5, 4 步骤3使用河豚算法对结果进行加密 Blowfish(secret_key, {0, 9, 3, 6, 2, 7, 5, 4}) 步骤4结合初始化向量和密文。 如果上一步中的Blowfish函数结果为1, 2, 3, 4, 5, 6, 7, 8 则密文为 1, 8, 2, 7, 3, 6, 4, 5, 1, 2, 3, 4, 5, 6, 7, 8 最后一块–填充 分组密码能够加密固定长度的消息而最后一个分组通常比该分组短。 由于密码无法加密因此我们需要一种在消息末尾添加其他字节的方法。 Shiro默认使用PKCS5填充。 每个字节等于填充的长度 如果最后一块太短请计算丢失的字节数并用该数字填充丢失的字节。 如果最后一个块的大小正确则将其视为丢失整个块的消息。 向其添加一个新的填充块。 每个字节都等于块大小。 示例1假设秘密消息有三个块我们正在尝试使用Blowfish-CBC对其进行加密。 最后的块是字节数组1, 1, 1, 1 。 填充块 1, 1, 1, 1, 4, 4, 4, 4 示例2假设该秘密消息有三个块我们正在尝试使用Blowfish-CBC对其进行加密。 最后一个块是字节数组8, 7, 6, 5, 4, 3, 2, 1 。 最后一块和填充 8, 7, 6, 5, 4, 3, 2, 1, 8, 8, 8, 8, 8, 8, 8, 8 进攻 最后我们准备展示对基于CBC的密码的两种不同攻击并证明问题是真实的。 我们的样本项目包含针对AES-CBC和Blowfish-CBC密码的两种攻击的测试案例。 第一部分说明如何将加密文本的开头更改为我们选择的任何消息。 第二小节介绍了如何解密密文。 数据篡改 仅当攻击者已经知道加密消息的内容时 才可能进行数据篡改攻击。 这样的攻击者可以将第一个消息块更改为他希望的任何内容。 特别是可以 更改AES-CBC加密消息的前16个字节 更改Blowfish-CBC加密邮件的前8个字节。 潜在危险 这种攻击是否危险在很大程度上取决于情况。 如果您使用密码通过网络发送密码那么数据篡改并不是那么危险。 最坏的情况是合法用户的登录将被拒绝。 同样如果加密的数据存储在某些只读存储中则不必担心数据被篡改。 但是如果要通过网络发送银行订单则数据篡改是真正的威胁。 如果有人将消息Pay Mark 100$更改为Pay Tom 9999$ Tom将获得9999 $而他不应该得到。 攻击 加密的消息分为三个部分 随机初始向量 第一组密文 消息的其余部分。 接收者使用块密文解密第一个密文块。 这给他message block?initial vector 。 要获取消息他必须将此值与初始向量进行异或运算 message block ? initial vector ? initial vector message block 初始向量与消息一起传输主动攻击者可以更改它。 如果攻击者用another iv替换了原始初始向量则接收者将解密另一条消息 message block ? initial vector ? another iv another message 重新排列前面的等式您将得到 another iv message block ? initial vector ? another message 如果攻击者同时知道加密消息的初始向量和内容则可以将消息修改为所需的任何内容。 他要做的就是将已知消息内容和所需消息都与原始初始向量异或。 解密修改后的密文的任何人都将获得修改后的消息而不是原始消息。 测试用例 我们创建了一个简单的测试案例演示了对使用AES-CBC加密的消息的这种攻击。 想象一下攻击者捕获了一封加密的电子邮件并且以某种方式知道其中的内容 //original message
private static final String EMAIL Hi,\n send Martin all requested money please.\n\n With Regards, \n Accounting\n; 攻击者只能更改消息的前16个字节因此他决定将资金重定向到Andrea //changed message
private static final String MODIFICATION Hi,\n give Andrea all requested money please.\n\n With Regards, \n Accounting\n; 以下测试用例对消息进行加密并修改密文。 修改后的密文被解密并与预期消息进行比较 Test
public void testModifiedMessage_AES() {//create cipher and the secret key StringCipherService cipher new StringCipherService(new AesCipherService());byte[] key cipher.generateNewKey();//encrypt the messagebyte[] ciphertext cipher.encrypt(EMAIL, key);//attack: modify the encrypted messagefor (int i 0; i 16; i) {ciphertext[i] (byte)(ciphertext[i] ^ MODIFICATION.getBytes()[i] ^ EMAIL.getBytes()[i]);}//decrypt and verifyString result cipher.decrypt(ciphertext, key);assertEquals(MODIFICATION, result);
} 当然可以对河豚CBC进行类似的攻击。 这次我们只能更改前8个字节 Test
public void testModifiedMessage_Blowfish() {String email Pay 100 dollars to them, but nothing more. Accounting\n;StringCipherService cipher new StringCipherService(new BlowfishCipherService());byte[] key cipher.generateNewKey();byte[] ciphertext cipher.encrypt(email, key);String modified Pay 900 dollars to them, but nothing more. Accounting\n;for (int i 0; i 8; i) {ciphertext[i] (byte)(ciphertext[i] ^ modified.getBytes()[i] ^ email.getBytes()[i]);}String result cipher.decrypt(ciphertext, key);assertEquals(modified, result);
} 解密密码 第二次攻击使攻击者可以解密该秘密消息。 仅当解密机密消息的应用程序与攻击者合作时才有可能进行攻击。 填充Oracle 攻击者会创建许多伪密文并将其发送给收件人。 当他尝试解密这些消息时将发生以下情况之一 密文解密为毫无意义的垃圾 修改后的消息将根本不是有效的密文。 如果应用程序在两种情况下的行为均相同则一切正常。 如果行为不同则攻击者能够解密密文。 基于CBC的密文只有一种错误的方法-如果填充错误。 例如如果密文包含加密的密码则当解密的密码错误时易受攻击的服务器可能会以“拒绝登录”进行响应而在密文无效的情况下则可能会出现运行时异常。 在这种情况下攻击者可以恢复密码。 大概的概念 每条虚假消息都有两个部分一个虚假的初始向量和一个消息块。 两者都发送到服务器。 如果它回答“正确填充”那么我们知道 message ? original iv ? fake iv valid padding 上述方程式中唯一未知的变量是消息。 original iv是先前的密文块 fake iv是由我们创建的有效填充是1或2, 2或3, 3, 3或...或8, 8, ..., 8等之一。 因此我们可以将块内容计算为 message valid padding ? original iv ? fake iv 算法 从恢复最后一个块字节开始。 每个伪初始向量都以0开头以不同的最后字节结尾。 这样我们几乎可以确定服务器仅在以1结尾的消息上回答“向右填充”。使用上一章公式计算最后一个块字节。 获取消息的倒数第二个字节非常相似。 唯一的区别是我们必须制作一个密文该密文解密为第二个最短的填充2, 2 。 消息的最后一个字节是已知的因此将2强制为最后一个值很容易。 初始向量的开头并不重要请将其设置为0。 然后我们尝试初始向量的倒数第二个字节的所有可能值。 服务器回答“正确填充”后我们可以从与前面相同的公式中获取倒数第二个消息字节 original iv ? fake iv ? 2 original iv ? fake iv ? 2 original iv ? fake iv ? 2 。 我们计算倒数第三个消息字节出来的假消息与填充3, 3, 3 ; 用填充4、4、4、4填充消息中的第四4, 4, 4, 4 以此类推直到整个块被解密为止。 测试用例 易受攻击的服务器使用PaddingOraculum类进行模拟。 此类的每个实例都会生成一个随机密钥并将其保密。 它仅公开两个公共方法 byte[] encrypt(String message) –用密钥加密一个字符串 boolean verifyPadding(byte[] ciphertext) –返回填充是否正确。 填充oraculum攻击是用decryptLastBlock方法实现的。 该方法解密加密消息的最后一块 private String decryptLastBlock(PaddingOraculum oraculum, byte[] ciphertext) {// extract relevant part of the ciphertextbyte[] ivAndBlock getLastTwoBlocks(ciphertext, oraculum.getBlockSize());// modified initial vectorbyte[] ivMod new byte[oraculum.getBlockSize()];Arrays.fill(ivMod, (byte) 0);// Start with last byte of the last block and // continue to the first byte.for (int i oraculum.getBlockSize()-1; i 0; i--) {// add padding to the initial vector int expectedPadding oraculum.getBlockSize() - i;xorPad(ivMod, expectedPadding);// loop through possible values of ivModification[i]for (ivMod[i] -128; ivMod[i] 127; ivMod[i]) {// create fake message and verify its paddingbyte[] modifiedCiphertext replaceBeginning(ivAndBlock, ivMod);if (oraculum.verifyPadding(modifiedCiphertext)) {// we can stop looping// the ivModification[i] // solution ^ expectedPadding ^ ivAndBlock[i]break;}}// remove the padding from the initial vectorxorPad(ivMod, expectedPadding);}// modified initial vector now contains the solution xor // original initial vectorString result ;for (int i 0; i ivMod.length; i) {ivMod[i] (byte) (ivMod[i] ^ ivAndBlock[i]);result (char) ivMod[i];}return result;
} 我们的示例项目包含两个测试用例 。 一个人用AES-CBC加密消息然后使用填充字眼到密文的最后一块。 另一个对河豚-CBC也做同样的事情。 解密Blowfish-CBC测试案例 Test
public void testPaddingOracle_Blowfish() {String message secret message!;PaddingOraculum oraculum new PaddingOraculum(new BlowfishCipherService());//Oraculum encrypts the message with a secret key.byte[] ciphertext oraculum.encrypt(message);//use oraculum to decrypt the messageString result decryptLastBlock(oraculum, ciphertext);//the original message had padding 1assertEquals(essage!(char)1, result);
} 资源资源 其他相关资源 免费在线斯坦福加密课程 。 从2002年开始用padding oracle攻击的原始文件。 填充oracle攻击的一个很好的替代解释 。 解释了对CBC的野兽袭击 。 它基于padding oracle攻击。 关于常见密码错误的堆栈交换线程 。 结束 没有密码可以绝对安全地防御所有可能的攻击。 相反它们仅提供针对定义明确的攻击类别的保护。 只有对系统的潜在威胁与密码强度匹配时密码才是安全的。 可以通过两种方式来防御主动攻击 通过设计使主动攻击不可能。 使用经过身份验证的加密。 使用经过身份验证的加密可以说更容易并且应该是首选。 排除主动攻击者容易出错而且风险更大。 Github上提供了本文中使用的所有代码示例。 参考 This is Stuff博客上的JCG合作伙伴 Maria Jurcovicova提供的Java安全加密 。 翻译自: https://www.javacodegeeks.com/2012/05/secure-encryption-in-java.html