一、概述
密码学是数学和计算机科学的分支,同时其原理涉及大量信息论。密码学的发展促进了计算机科学,特别是在于电脑与网络 安全所使用的技术,如访问控制与信息的机密性。信息的加密几乎在所有的信息通讯中都是很有必要的,尤其是金融类、机密类信息。所以,了解密码学和加密算法对于开发人员来说,也是很有必须的。
摘要算法是一种单向加密算法,例如SHA、MD5等,这是一种数据完整性的加密,这种单向加密一般称为摘要,而不是真正意义上的加密。因为摘要不可逆,可以防止数据在传输过程中被篡改。可以用于用户登录时验证密码、接口请求时验证参数、文件传输时验证完整性等。
对称加密算法是一种基于密钥的加密与解密算法,例如DES、AES等,通信双方共享同一个密钥,双方在通信前商定该密钥,并妥善保管。对称加密有两种方式,一种基于固定密钥,另一种是基于随机源密钥。
非对称加密算法中加密使用的密钥和解密使用的密钥不同,一个公开的称为公钥,一个保密的称为私钥。通信双方都有一对自己的公私钥,私钥自己保管好,公钥提供给对方,例如DSA、RSA等。非对称加密的应用场景有两种,一种是公钥加密,私钥解密,另一种是私钥加密,公钥解密。
对称加密和非对称加密
对称加密速度快、算法公开、计算量小、加密速度快、加密效率高,适用于加密大量数据,但通讯双方需要保护好密钥,存在密钥泄漏的安全风险,而且通讯方的密钥都不一样,所以所拥有的密钥数量巨大,密钥的管理会成为负担。
非对称加密公钥是公开的,私钥不对外公开,只需要维护自己的一组公钥和私钥,在密钥管理上更便利。非对称加密算法强度复杂,加解密算法速度没有对称加密的速度快,但这也保证了其安全性。
针对两种加密方式的优缺点,各大密码机构主张对称加密与非对称加密算法结合使用,使用对称加密算法加密内容,再使用非对称加密算法加密对称算法的密钥。
二、应用场景
MD5实现用户登录
在设计一个用户登录系统时,用户的密码不管是在网络传输中,还是存在数据库中,都不应该是明文。一般会选用摘要算法来对密码进行加密,在传输和存储时都是使用加密后的Hash密文。比如使用MD5对密码进行加密,在注册时将密码明文通过MD5加密,然后存储在数据库中,登录时客户端同样对密码进行加密,然后在服务端与数据库中的Hash密文对比,通过判断两个Hash密文是否一致来决定是是否登录成功。这样处理的好处是,即使数据库中的密码泄漏了,也不会知道密码明文。
只要明文相同,MD5加密后的密文就相同,于是攻击者就可以通过撞库的方式来破解出明文。加盐就是向明文中加入随机数,然后在生成MD5,这样一来即使明文相同,每次生成的MD5码也不同,如此就加大了暴力破解的难度。
要算法实现参数验签
摘要算法可以任意大的数据压缩成摘要,使得数据量变小,将数据的格式转换成固定位数的Hash码。这可以给任何一种数据创建小的数字“指纹”,这就可以用来判断数据的完整性,判断数据在传输过程中是否被篡改,从而满足安全性需要。例如,我们要设计有一定安全性要求的接口,需要对接口参数进行完整性验证,可以在请求参数中添加一个字段存放所有业务字段的摘要Hash码,这样做的话,即使在接口请求的过程中被黑客攻击修改了参数信息,也不会成功请求服务,从而破坏正常业务。
Git保证完整性
Git中所有数据在存储前都计算校验和,然后以校验和来引用。这意味着不可能在Git不知情时更改任何文件内容或目录内容。这个功能建构在Git底层,是构成Git哲学不可或缺的部分。若你在传送过程中丢失信息或损坏文件,Git就能发现。Git用以计算校验和的机制叫做SHA-1散列(摘要算法),它会基于Git中文件的内容或目录结构计算出来一个Hash码。
24b9da6552252987aa493b52f8696cd6d3b00373
Git中使用这种Hash码的情况很多,实际上,Git数据库中保存的信息都是以文件内容的Hash码来索引,而不是文件名。
非对称加密实现数字签名
数字签名是一种功能类似写在纸上的普通签名,使用非对称加密领域的技术,一套数字签名算法通常包括两种运算,一个用于签名,另一个用于验证。在使用非对称加密时,通常我们使用公钥加密,用私钥解密,而在数字签名中,我们使用私钥加密(生成签名),公钥解密(验证签名)。前面说过非对称加密算法的复杂性,所以通常我们会对消息内容的Hash值签名,因为Hash值长度远远小于消息原文,使得签名(非对称加密)的效率大大提高。
公钥和私钥都可以用于加解密操作,用公钥加密的数据只能由对应的私钥解密,反之亦然。虽说两者都可用于加密,但是不同场景使用不同的密钥来加密,当私钥用于签名,公钥用于验签时,签名和加密作用不同,签名并不是为了保密,而是为了保证这个签名是由特定的某个人签名的,而不是被其它人伪造的签名,所以私钥的私有性就适合用在签名用途上。私钥签名后,只能由对应的公钥解密,公钥又是公开的(很多人可持有),所以这些人拿着公钥来解密,解密成功后就能判断出是持有私钥的人做的签名,验证了身份合法性。
公钥加密,私钥解密,才是真正的加密;私钥加密,公钥解密用于签名。
SSH协议加密分析
SSH是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。SSH最常见的用途是远程登录系统,通常用来传输命令行界面和远程执行命令。SSH的使用在类Unix系统(Linux系统)最为常见,不过在Windows10 1809版本也提供了Open SSH工具。在使用SSH客户端登录时,SSH提供两种级别的安全验证:
- 第一种级别(基于密码的安全验证),使用帐号和密码登录到远程主机,并且所有传输的数据都会被加密。但是,可能会有别的服务器在冒充真正的服务器,无法避免被“中间人”攻击。
- 第二种级别(基于密钥的安全验证),需要依靠密钥,也就是你必须为自己创建一对密钥,并把公有密钥放在需要访问的服务器上。客户端会向服务器发出请求,请求用你的密钥进行安全验证。服务器收到请求之后,先在你在该服务器的用户根目录下寻找你的公有密钥,然后把它和你发送过来的公有密钥进行比较。如果两个密钥一致,服务器就用公有密钥加密“质询”(challenge)并把它发送给客户端软件。从而避免被“中间人”攻击。
SSH使用密钥验证时,使用了摘要算法和非对称加密算法,已许可登录的公钥保存在用户的 ~/.ssh/authorized_keys
文件中。
三、实现案例
1. SHA
安全散列算法(SHA)是一个密码散列函数家族,是FIPS所认证的安全散列算法。能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的几率很高。实现代码如下:
public class Main { public static void main(String[] args) throws Exception { MessageDigest sha = MessageDigest.getInstance("SHA"); sha.update("码匠公众号".getBytes()); System.out.println(byteToString(sha.digest())); MessageDigest sha224 = MessageDigest.getInstance("SHA-224"); sha224.update("码匠公众号".getBytes()); System.out.println(byteToString(sha224.digest())); MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); sha256.update("码匠公众号".getBytes()); System.out.println(byteToString(sha256.digest())); } private static String byteToString(byte[] arr) { StringBuilder res = new StringBuilder(); for (byte b : arr) { res.append(String.format("%02X", b)); } return res.toString(); } }
2. MD5
MD5消息摘要算法,一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致,将数据(如一段文字)运算变为另一固定长度值,是散列算法的基础原理。代码实现如下:
public class Main { public static void main(String[] args) throws Exception { MessageDigest sha = MessageDigest.getInstance("MD5"); sha.update("码匠公众号".getBytes()); System.out.println(byteToString(sha.digest())); } private static String byteToString(byte[] arr) { ...... } }
MD5算法无法防止碰撞,因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。
3. DES
数据加密标准(DES)是一种对称密钥加密块密码算法,实现代码如下:
public class Main { public static void main(String[] args) { String str = "码匠公众号"; String password = "CodeArtist"; byte[] encrypt = encrypt(str.getBytes(), password); System.out.println("密文: " + byteToString(encrypt)); byte[] decrypt = decrypt(encrypt, password); System.out.println("明文: " + new String(decrypt)); } /** * 加密 */ public static byte[] encrypt(byte[] src, String password) { try { SecureRandom random = new SecureRandom(); DESKeySpec desKey = new DESKeySpec(password.getBytes()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey secureKey = keyFactory.generateSecret(desKey); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, secureKey, random); return cipher.doFinal(src); } catch (Throwable e) { e.printStackTrace(); } return null; } /** * 解密 */ public static byte[] decrypt(byte[] encrypt, String password) { try { SecureRandom random = new SecureRandom(); DESKeySpec desKey = new DESKeySpec(password.getBytes()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey secureKey = keyFactory.generateSecret(desKey); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, secureKey, random); return cipher.doFinal(encrypt); } catch (Exception e) { e.printStackTrace(); } return null; } private static String byteToString(byte[] arr) { ...... } }
4. AES
高级加密标准(AES)是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。实现代码如下:
public class Main { public static void main(String[] args) { String str = "码匠公众号"; String password = "CodeArtist"; byte[] encrypt = encrypt(str.getBytes(), password); System.out.println("密文: " + byteToString(encrypt)); byte[] decrypt = decrypt(encrypt, password); System.out.println("明文: " + new String(decrypt)); } /** * 加密 */ public static byte[] encrypt(byte[] src, String password) { try { KeyGenerator keygen = KeyGenerator.getInstance("AES"); keygen.init(128, new SecureRandom(password.getBytes())); byte[] raw = keygen.generateKey().getEncoded(); SecretKey key = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance(keygen.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, key); return cipher.doFinal(src); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 解密 */ private static byte[] decrypt(byte[] encrypt, String password) { try { KeyGenerator keygen = KeyGenerator.getInstance("AES"); keygen.init(128, new SecureRandom(password.getBytes())); byte[] raw = keygen.generateKey().getEncoded(); SecretKey key = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance(keygen.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, key); return cipher.doFinal(encrypt); } catch (Exception e) { e.printStackTrace(); } return null; } private static String byteToString(byte[] arr) { ...... } }
5. DSA
数字签名算法(DSA)是用于数字签名的联邦信息处理标准,该算法使用由公钥和私钥组成的密钥对。私钥用于生成消息的数字签名,并且可以通过使用签名者的相应公钥来验证这种签名。数字签名提供信息鉴定(接收者可以验证消息的来源),完整性(接收方可以验证消息自签名以来未被修改)和不可否认性(发送方不能错误地声称它们没有签署消息)。实现代码如下:
public class Main { public static void main(String[] args) throws Exception { String data = "码匠公众号"; KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(512); KeyPair keypair = kpg.generateKeyPair(); DSAPublicKey publicKey = (DSAPublicKey) keypair.getPublic(); DSAPrivateKey privateKey = (DSAPrivateKey) keypair.getPrivate(); Signature signature = Signature.getInstance("SHA1withDSA"); byte[] sign = sign(signature, data.getBytes(), privateKey); boolean result = verify(signature, data.getBytes(), sign, publicKey); System.out.println(result); } /** * 签名 */ public static byte[] sign(Signature signature, byte[] data, DSAPrivateKey privateKey) { try { signature.initSign(privateKey); signature.update(data); return signature.sign(); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 验证 */ public static boolean verify(Signature signature, byte[] data, byte[] sign, DSAPublicKey publicKey) { try { signature.initVerify(publicKey); signature.update(data); return signature.verify(sign); } catch (Exception e) { e.printStackTrace(); } return false; } }
6. RSA
RSA加密算法是一种非对称加密算法,在公开密钥加密和电子商业中被广泛使用。对极大整数做因数分解的难度决定了RSA算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用RSA加密的信息的可靠性就会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的RSA钥匙才可能被强力方式破解。到当前为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被破解的。实现代码如下:
public class Main { public static void main(String[] args) throws Exception { String data = "码匠公众号"; KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(1024); KeyPair keypair = kpg.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keypair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keypair.getPrivate(); byte[] encrypt = encrypt(data.getBytes(), publicKey); System.out.println("密文: " + byteToString(encrypt)); byte[] decrypt = decrypt(encrypt, privateKey); System.out.println("明文: " + new String(decrypt)); System.out.println(); } /** * 加密 */ public static byte[] encrypt(byte[] src, RSAPublicKey publicKey) { try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey key = keyFactory.generatePublic(new X509EncodedKeySpec(publicKey.getEncoded())); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, key); return cipher.doFinal(src); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 解密 */ public static byte[] decrypt(byte[] encrypt, RSAPrivateKey privateKey) { try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey key = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey.getEncoded())); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, key); return cipher.doFinal(encrypt); } catch (Exception e) { e.printStackTrace(); } return null; } private static String byteToString(byte[] arr) { ...... } }
四、英文缩写对照
英文缩写 | 英文全拼 | 中文 |
SHA | Secure Hash Algorithm | 安全散列算法 |
MD | Message Digest | 信息摘要 |
DES | Data Encryption Standard | 数据加密标准 |
AES | Advanced Encryption Standard | 高级加密标准 |
DSA | Digital Signature Algorithm | 数字签名算法 |
RSA | - | 一种非对称加密算法 |
SSH | Secure Shell | 安全外壳协议 |