在整个技术生态系统中引起反响的一个重大事件是2018年推出的GDPR,即欧洲公民的通用数据保护法规。虽然我将会或不会影响英国公民,但它仍然有待观察,它为Java开发人员提供了一个积极的机会,可以对三个安全领域感兴趣:
-
在其应用程序中查找个人身份信息(PII)或其他敏感数据。
-
正确使用加密技术,以正确保护此信息和安全系统。
-
应用自定义代码,库和JRE的有效补丁管理。
查找PII和敏感数据
Java开发人员可以通过静态类型和清除API来定位PII。对于开发人员编写的POJO的例子,标准化的方法命名通过大部分的社区可以揭示潜在的PII信息一样 getAddress()
, getName()
或 getSomethingElseThatLooksLikePII()
。通过方法名称,熟悉PII目标的开发人员可以轻松找到PII睡眠的兴趣点,并识别潜在的泄漏。
Java API还提供了另一种识别PII熊洞穴的简单方法。例如,大多数持久性由JDBC或ORM库(如Hibernate)处理。通过观察查询或直接查看数据库,开发人员可以识别流经这些接口的PII。标准化API(如JDBC和Hibernate)的存在使得基于仪器的工具可以轻松地找到PII,类似于免费的基于仪器的工具监控安全性或性能的方式。
正确使用密码学
Java开发人员受益于Java Cryptographic Architecture,这是一套兼容的工具和库,涵盖了基本的加密方法。从JDK 9开始,大多数JRE都附带了Unlimited Strength Cryptography。无限强度的加密强度和策略文件配置的限制一直是需要强加密的开发项目混淆的根源。Java开发人员提供了一个快速测试,您可以运行以了解是否正确配置了无限强度。
导入 javax。加密。密码 ;
class TestCryptoLimitations {
public static void main(String [] args){
尝试 {
系统。出。println(“Hello World!”);
int maxKeyLen = 密码。getMaxAllowedKeyLength(“AES”);
系统。出。println(“最大 密钥 长度 为 ” + maxKeyLen);
} catch(例外 e){
系统。出。println(“悲伤的世界:(”);
}
}
}
接下来,您可能会在项目中遇到三个主要的加密用例:散列文件/凭证,对称加密和非对称加密。
哈希和消息摘要
散列通常用于生成文件校验和或加密凭据。它是一种单向算法,从密钥计算哈希值可以在多项式时间内完成,并且被认为是有效的计算。相反的操作,找到产生给定散列的值,称为碰撞,效率不高且计算成本高。考虑生日攻击以O(2 ^(n / 2)时间为例。单向散列函数的行为使它们适合于凭证存储或测试数据(例如,文件)篡改。即使是一个小的改变文件将显着更改哈希值。
常见的哈希算法有:SHA-256,SHA-512或较旧的算法,如MD5和SHA-1,可以在jdk.certpath.disabledAlgorithms属性下的jre / lib / security / java.security文件中列入黑名单。
在对凭证的秘密部分进行散列(例如,密码)时,散列值应包括每用户盐的唯一值。盐是加密随机值,难以猜测,通常采用长串十六进制值或字节数组的形式。包含盐可防止预计算攻击,攻击者只需在称为彩虹表的字典中查找预先计算的值。不需要Salting来散列非秘密信息,例如文件检查。最好使用像PBKDF2这样的密钥派生函数而不是自己的凭证散列,我们将很快介绍。
计算给定文件的哈希值:
MessageDigest md = MessageDigest。getInstance(“SHA - 256 ”);
md。更新(fileinbytes);
final byte [] hashed = md。doFinal();
这可以使用基于密码的密钥推导(PBKDF2)来生成信息的散列,这些信息应该是秘密的并且更难以预先计算。
final String password = “12345” ;
final String salt = “user@example.com” ;
final int iterations = 32 ;
PBEKeySpec keySpec = 新 PBEKeySpec(密码。toCharArray(),盐。的getBytes(),次迭代,512);
SecretKeyFactory skf = SecretKeyFactory。getInstance(“PBKDF2WithHmacSHA256”);
byte [] hashed = skf。generateSecret(keySpec)。getEncoded();
final String encoded = Base64。getEncoder()。encodeToString(hashed);
系统。出。println(“编码:” + 编码);
对称密码
对称密码用于加密和解密信息。对称密码得名,因为加密和解密操作使用相同的密钥。对称密码术是密码学的基础,比非对称密码学更快。密钥通常是长字符串或字节数组,在字典中不容易找到。
常见的对称算法包括AES,Blowfish和DES。以下是对称加密和解密的示例。
导入 javax。加密。密码 ;
导入 javax。加密。规范。IvParameterSpec ;
导入 javax。加密。规范。SecretKeySpec ;
导入 java。安全。SecureRandom ;
导入 java。util。Base64 ;
public class CryptoAdvent {
public static String encrypt(byte [] key,byte [] initVector,String value)throws Exception {
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key,“AES”);
密码 密码 = 密码。getInstance(“AES / CBC / PKCS5PADDING”);
密码。INIT(密码。ENCRYPT_MODE,skeySpec,IV);
byte [] encrypted = cipher。doFinal(值。的getBytes(“UTF-8” ));
字符串 编码 = Base64。getEncoder()。encodeToString(加密);
返回 编码 ;
}
public static String decrypt(byte [] key,byte [] initVector,String encrypted)throws Exception {
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key,“AES”);
密码 密码 = 密码。getInstance(“AES / CBC / PKCS5PADDING”);
密码。INIT(密码。DECRYPT_MODE,skeySpec,IV);
byte [] original = cipher。doFinal(Base64的。getDecoder()。解码(加密));
return new String(original);
}
private static String bytesToHex(byte [] bytes){
StringBuilder sb = new StringBuilder();
for(byte b:bytes){
SB。追加(字符串。格式(“%02X” ,b));
}
返回 某人。toString();
}
public static void main(String [] args){
尝试 {
//注意:每次程序运行时都会生成一个新的Key和initVector。真实的
//实现你需要将密钥和initVector存储为秘密
//以后解密。
//
SecureRandom sr = new SecureRandom();
byte [] key = new byte [ 16 ];
sr。nextBytes(key); // 128位密钥
byte [] initVector = new byte [ 16 ];
sr。nextBytes(initVector); // 16字节IV
系统。出。println(“Random key =” + bytesToHex(key));
系统。出。println(“initVector =” + bytesToHex(initVector));
String payload = “这是Erik和Milton的文章的明文。” ;
系统。出。println(“Original text =” + payload);
String encrypted = encrypt(key,initVector,payload);
系统。出。println(“加密文本=” + 加密);
串 解密 = 解密(键,initVector,加密);
系统。出。println(“Decrypted text =” + decrypted);
字符串 结果 =已 解密。等于(有效载荷)? “有用!” :“事情不对。” ;
系统。出。println(结果);
} catch(Exception t){
t。printStackTrace();
}
}
}
一个示例输出是:
随机 键= E5 01 B6 AC 9 C A5 6 D 64 08 DE AB DD 83 9 C E0 87
initVector = AC 09 5 C B0 6 E 76 3 B E6 A4 2 B D7 4 C B3 4 B CE F8
原始 文本= 这 是 在 明文 从 埃里克 和 米尔顿的文章。
加密 文本= / LQlJp7fR4Gkq5unWU4X + 5 qrje1WWKyCms + MPzcwsFf2eE + QHVr2RQDoJVfrSmoc / dM5ulrtk5z4z4evozprUQ ==
解密 文本= 这 是 在 明文 从 埃里克 和 米尔顿的文章。
它的 作品!
不对称密码
大多数Internet加密(例如HTTPS)都是围绕非对称加密构建的。非对称加密技术的优点在于它为客户端和服务器提供了一种安全地协商密钥和密码套件的方法,这些秘密密钥和密码套件后来用于使用通用的对称加密技术来保护数据。非对称加密利用一些新的类,如中 KeyPair
, PublicKey
, PrivateKey
,和 Certificate
。这些类中的每一个都设计为可以共享公钥以便通过不受信任的网络访问任何人,然后使用他们自己的私钥加密消息。然后,公钥的所有者可以使用他们自己的私钥解密消息,使两者能够通话。
非对称加密的最常见用途是与网站进行加密的HTTPS通信。在这种情况下,每个客户端都有一个已知的证书颁发机构列表,用于建立身份并验证每个网站的公钥的所有者。
与浏览器不同,大多数JRE都有一个截断的证书颁发机构列表,其中包含许多来自Digicert的不同证书,但不包含流行的证书颁发机构,如Amazon Trust(AWS)或SSL.com。因此,Java客户端可能会因PKIX异常而无法对某些URL进行身份验证。
发生这种情况时,开发人员不应只是禁用SSL身份验证。这样做可以简单地确保应用程序通过安全通道发送信息,而无需知道此通道另一端的内容。
正确的机制是将根证书添加到lib / security / cacerts文件中:
keytool -importcert -keystore lib/security/cacerts -alias awstrust -file awstrust.cer
虽然将根证书添加到此根证书颁发机构存储是合理的,但是耳机之类的东西不需要根证书,如果是,则这些耳机的私钥应保持私密。
有效的补丁管理
开发人员需要考虑两个方面进行修补:
-
保持与JRE的节奏
-
跟上库漏洞
Oracle JRE和Amazon Corretto都按季度进行补丁。与Oracle JRE(其Java 8支持在2019年4月结束)不同,Amazon Corretto承诺在2023年之前按季度免费修补补丁 - 大约四年。
Java开发人员无法抵御最近攻击Node社区的攻击类型,从而窃取了不同数量的比特币。例如,在2014年,针对Maven Central进行了一次攻击,以修改字节码,因为它在jar文件中移动到网络中。虽然响应很快就能启用SSL,但大多数JAR文件仍然没有通过jarsigner工具签名,这使得检测篡改变得更加困难。类似的攻击在别处发生。
当在第三方库中发现漏洞时,在部署补丁之前,您的应用程序几乎没有防御。这就是为快速修补设计软件应用程序,服务和基础架构对于强大的安全性至关重要的原因。除了快速部署之外,测试通常是一个常见的障碍。大多数组织推迟修补,因为他们没有很大的信心推动修补程序不会破坏生产。这就是为什么投资高质量的单元测试案例非常重要的原因。
为了监控第三方库的安全状态,OWASP Dependency Check或Contrast Community Edition等开发人员可以使用免费工具。
许多软件购买者强制执行这种依赖性分析,称为软件组合分析,部分原因是只需查看库就可以轻松检测到。忽视此建议的开发人员可能会发现他们的应用程序取消部署,后续发票未付。
一般建议
开发人员应该关注他们拥有的数据类型以及他们如何保护这些数据。由于GDPR的监管环境,“保存所有数据”的默认业务方法可能成为严重的商业责任。数据可能是新油,但油是易燃的,并且在处理加密数据时,不要将密钥与加密数据一起存储,因为这会破坏目的。