常见的加密算法
这些算法的加密对象都是基于二进制数据,如果要加密字符串就使用统一编码(如:utf8)进行编码后加密。
对称性加密算法:对称式加密就是加密和解密使用同一个密钥。信息接收双方都需事先知道密匙和加解密算法且其密匙是相同的,之后便是对数据进行加解密。对称加密算法用来对敏感数据等信息进行加密。
非对称算法:非对称式加密就是加密和解密所使用的不是同一个密钥,通常有两个密钥,称为"公钥"和"私钥",两个必需配对使用,否则不能打开加密文件。发送双方A,B事先均生成一对密匙,然后A将自己的公有密匙发送给B,B将自己的公有密匙发送给A,如果A要给B发送消 息,则先需要用B的公有密匙进行消息加密,然后发送给B端,此时B端再用自己的私有密匙进行消息解密,B向A发送消息时为同样的道理。
散列算法:散列算法,又称哈希函数,是一种单向加密算法。在信息安全技术中,经常需要验证消息的完整性,散列(Hash)函数提供这一服务,对不同长度的输入消息,产生固定长度的输出。这个固定长度的输出称为原输入消息的"散列"或"消息摘要"(Message digest)。散列算法不算加密算法,因为其结果是不可逆的,既然是不可逆的,那么当然不是用来加密的,而是签名。
非对称性算法有:RSA、DSA、ECC
RSA:由 RSA 公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的。
DSA(Digital Signature Algorithm):数字签名算法,一个消息的加密哈希(MD5或者SHA)被创建后,哈希值用发送者的私钥加密,加密的结果就是叫做数字签名。
ECC(Elliptic Curves Cryptography):椭圆曲线密码编码学。ECC和RSA相比,具有多方面的绝对优势,主要有:抗攻击性强。相同的密钥长度,其抗攻击性要强很多倍。计算量小,处理速度快。ECC总的速度比RSA、DSA要快得多。存储空间占用小。ECC的密钥尺寸和系统参数与RSA、DSA相比要小得多,意味着它所占的存贮空间要小得多。这对于加密算法在IC卡上的应用具有特别重要的意义。带宽要求低。当对长消息进行加解密时,三类密码系统有相同的带宽要求,但应用于短消息时ECC带宽要求却低得多。带宽要求低使ECC在无线网络领域具有广泛的应用前景。
散列算法(签名算法)有:MD5、SHA1、HMAC
用途:主要用于验证,防止信息被修。具体用途如:文件校验、数字签名、鉴权协议
MD5:MD5是一种不可逆的加密算法,对应任何字符串都可以加密成一段唯一的固定长度的代码。
SHA1:是由NISTNSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1设计时基于和MD4相同原理,并且模仿了该算法。SHA-1是由美国标准技术局(NIST)颁布的国家标准,是一种应用最为广泛的Hash函数算法,是目前最先进的加密技术,被政府部门和私营业主用来处理敏感的信息。而SHA-1基于MD5,MD5又基于MD4。
HMAC:是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。也就是说HMAC是需要一个密钥的。所以,HMAC_SHA1也是需要一个密钥的,而SHA1不需要
摘要算法
常用的摘要算法有MD5,SHA1。摘要算法是一个不可逆过程,就是无论多大数据,经过算法运算后都是生成固定长度的数据,一般结果使用16进制进行显示。
MD5和SHA1的区别:MD5结果是128位摘要,SHa1是160位摘要。那么MD5的速度更快,而SHA1的强度更高。
主要用途有:验证消息完整性,安全访问认证,数据签名。
消息完整性:由于每一份数据生成的MD5值不一样,因此发送数据时可以将数据和其MD5值一起发送,然后就可以用MD5验证数据是否丢失、修改。
安全访问认证:这是使用了算法的不可逆性质,(就是无法从MD5值中恢复原数据)对账号登陆的密码进行MD5运算然后保存,这样可以保证除了用户之外,即使数据库管理人员都无法得知用户的密码。
数字签名:这是结合非对称加密算法和CA证书的一种使用场景。
MD5(单向散列算法) 全称是Message-Digest Algorithm 5(信息-摘要算法)
MD5功能:
输入任意长度的信息,经过处理,输出为128位的信息(数字指纹);
不同的输入得到的不同的结果(唯一性);
根据128位的输出结果不可能反推出输入的信息(不可逆);
MD5安全性:
普遍认为MD5是很安全,因为暴力破解的时间是一般人无法接受的。实际上如果把用户的密码MD5处理后再存储到数据库,其实是很不安全的。
一般破解方法:字典法,就是将常用密码生成MD5值字典,然后反向查找达到破解目的,因此建议使用强密码。
防止抵赖(数字签名):
需要一个第三方认证机构。例如A写了一个文件,认证机构对此文件用MD5算法产生摘要信息并做好记录。若以后A说这文件不是他写的,权威机构只需对此文件重新产生摘要信息,然后跟记录在册的摘要信息进行比对,相同的话,就证明是A写的。这就是所谓的“数字签名”。
MD5算法过程:
对MD5算法简要的叙述可以为:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值
Java实现MD5算法
MessageDigest 类为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。信息摘要是安全的单向哈希函数,它接收任意大小的数据,输出固定长度的哈希值。 MessageDigest 对象开始被初始化。该对象通过使用 update 方法处理数据。任何时候都可以调用 reset 方法重置摘要 一旦所有需要更新的数据都已经被更新,应该调用 digest 方法之一完成哈希计算 对于给定数量的更新数据,digest 方法只能被调用一次。digest 被调用后,MessageDigest 对象被重新设置成其初始状态。 import java.security.MessageDigest; public class MyMD5 { static char[] hex = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; public static void main(String[] args) { try{ MessageDigest md5 = MessageDigest.getInstance("MD5");//申明使用MD5算法 md5.update("a".getBytes());// System.out.println("md5(a)="+byte2str(md5.digest())); md5.update("a".getBytes()); md5.update("bc".getBytes()); System.out.println("md5(abc)="+byte2str(md5.digest())); }catch(Exception e){ e.printStackTrace(); } } /** * 将字节数组转换成十六进制字符串 * @param bytes * @return */ private static String byte2str(byte []bytes){ int len = bytes.length; StringBuffer result = new StringBuffer(); for (int i = 0; i < len; i++) { byte byte0 = bytes[i]; result.append(hex[byte0 >>> 4 & 0xf]); // 见下面解释。 result.append(hex[byte0 & 0xf]); } return result.toString(); } } 其中,>>> 是无符号右移,忽略符号位,空位都以0补齐. 上面两句位移的解释:md5是128位的。一个byte是8个字节。所以一共是16个byte。而md5结果一般是以32个ascii字符出现的。所以每个byte会表示成2个字符。 这就是上面每个byte会处理两次,分别进行hex的原因。
MD5的使用—对文件进行摘要
//对文件进行MD5摘要 public static String getMD5(String path){ String pathName = path; String md5= ""; try { File file = new File(pathName); FileInputStream ins = new FileInputStream(file); FileChannel ch = ins.getChannel(); MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0,file.length()); MessageDigest md = MessageDigest.getInstance("MD5"); md.update(byteBuffer); ins.close(); md5 = toHexString(md.digest()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return md5; } //以16进制编码进行输出 final static char hex[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; public static String toHexString(byte[] tmp){ String s; char str[] = new char[tmp.length*2]; int k =0; for (int i = 0; i < tmp.length; i++) { byte byte0 = tmp[i]; str[k++] = hex[byte0>>>4&0xf]; str[k++] = hex[byte0&0xf]; } s=new String(str); return s; }
//对文件进行SHA1摘要 public static String getSHA1(String path){ String pathName = path; String sha1= ""; try { File file = new File(pathName); FileInputStream ins = new FileInputStream(file); FileChannel ch = ins.getChannel(); MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0,file.length()); MessageDigest sha = MessageDigest.getInstance("SHA-1"); sha.update(byteBuffer); ins.close(); sha1 = toHexString(sha.digest()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return sha1; }
对称加密算法
对称加密算法只是为了区分非对称加密算法。特点是对称加密是加密解密使用相同的密钥,而非对称加密加密和解密时使用的密钥不一样。对于大部分情况使用对称加密,而对称加密的密钥交换时使用非对称加密,这有效保护密钥的安全。非对称加密加密和解密密钥不同,那么它的安全性是无疑最高的,但是它加密解密的速度很慢,不适合对大数据加密。而对称加密加密速度快,因此混合使用最好。
常用的对称加密算法有:AES和DES.
DES:比较老的算法,一共有三个参数入口(原文,密钥,加密模式)。而3DES只是DES的一种模式,是以DES为基础更安全的变形,对数据进行了三次加密,也是被指定为AES的过渡算法。
AES:高级加密标准,新一代标准,加密速度更快,安全性更高(优先选择)
AES的使用
AES密钥长度可以选择128位【16字节】,192位【24字节】和256位【32字节】密钥。
/**使用AES对字符串加密 * @param str utf8编码的字符串 * @param key 密钥(16字节) * @return 加密结果 * @throws Exception */ public static byte[] aesEncrypt(String str, String key) throws Exception { if (str == null || key == null) return null; Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes("utf-8"), "AES")); byte[] bytes = cipher.doFinal(str.getBytes("utf-8")); return bytes; } /**使用AES对数据解密 * @param bytes utf8编码的二进制数据 * @param key 密钥(16字节) * @return 解密结果 * @throws Exception */ public static String aesDecrypt(byte[] bytes, String key) throws Exception { if (bytes == null || key == null) return null; Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes("utf-8"), "AES")); bytes = cipher.doFinal(bytes); return new String(bytes, "utf-8"); }
注意AES算法的所有参数都是字节码(包括密钥)。字符串字符需要转换成字节码后进行加密str.getBytes("utf-8")按照字符串的编码进行转换。另外参数:”AES/ECB/PKCS5Padding”在加密和解密时必须相同,可以直接写”AES”,分别的意思为:AES是加密算法,ECB是工作模式,PKCS5Padding是填充方式。
AES是分组加密算法,也称块加密。每一组16字节。这样明文就会分成多块。当有一块不足16字节时就会进行填充。
一共有四种工作模式:
ECB 电子密码本模式:相同的明文块产生相同的密文块,容易并行运算,但也可能对明文进行攻击。
CBC 加密分组链接模式:一块明文加密后和上一块密文进行链接,不利于并行,但安全性比ECB好,是SSL,IPSec的标准。
CFB 加密反馈模式:将上一次密文与密钥运算,再加密。隐藏明文模式,不利于并行,误差传递。
OFB 输出反馈模式:将上一次处理过的密钥与密钥运算,再加密。隐藏明文模式,不利于并行,有可能明文攻击,误差传递。
PKCS5Padding的填充方式是差多少字节就填数字多少;刚好每一不足16字节时,那么就会加一组填充为16.还有其他填充模式【Nopadding,ISO10126Padding】(不影响算法,加密解密时一致就行)
DES的使用
3DES指定为”DESede”,DES密钥长度是56位,3DES加长了密钥长度,可以为112位或168位,所以安全性提高,速度降低。工作模式和填充模式标准和AES一样。
/**使用DES对字符串加密 * @param str utf8编码的字符串 * @param key 密钥(56位,7字节) * @return 加密结果 * @throws Exception */ public static byte[] desEncrypt(String str, String key) throws Exception { if (str == null || key == null) return null; Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes("utf-8"), "DES")); byte[] bytes = cipher.doFinal(str.getBytes("utf-8")); return bytes; } /**使用DES对数据解密 * @param bytes utf8编码的二进制数据 * @param key 密钥(16字节) * @return 解密结果 * @throws Exception */ public static String desDecrypt(byte[] bytes, String key) throws Exception { if (bytes == null || key == null) return null; Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes("utf-8"), "DES")); bytes = cipher.doFinal(bytes); return new String(bytes, "utf-8"); }
非对称加密(RSA)
非对称加密解密时使用不同的密钥,分为公钥(public key)和私钥(private key).公钥可以公开,而私钥自己保存。利用的是两个大质数相乘十分容易,而对其乘积进行因素分解十分困难。这样就可以将乘积作为密钥了,这个乘积为N值,根据两个大质数选择e和生成d,删掉两个大质数。这样(N,e)为公钥,(N,d)为私钥,公钥无法破解出私钥。由于非对称加密的密钥生成麻烦,所以无法做到一次一密,而且其加密速度很慢,无法对大量数据加密。最常用的使用场景就是数字签名和密码传输,用作数字签名时使用私钥加密,公钥解密;用作加密解密时,使用公钥加密,私钥解密。
注意RSA加密是有长度限制的,1024位密钥可以加密128字节(1024位),不满128字节的使用随机数填充,但是RSA实现中必须要加随机数(11字节以上),所以明文长度最大为117字节,然后剩下的加入随机数。这也产生了每次加密结果每一次都不一样的特点。
如果明文长度超过限制怎么办?
1.可以与对称加密混合使用,一次一密产生对称加密的密钥,然后使用此密钥进行数据对称加密,再使用RSA私钥对对称密钥加密,一起保存。解密时使用公钥解密出密钥,然后进行数据解密。
2.可以分段加密。将明文按117字节分成多段,加密后再拼接起来。由于每一段密文长度都是128字节,所以解密时按照128字节分段解密。
Java的RSA密钥生成与使用
下面是java中的使用方法,先是生成密钥对,然后加密,再解密。需要注意的是这个方法是不能跨语言使用的,因为里面对公钥和私钥用到的序列化是java的序列化。
由于加密后的密文都是字节码形式的,要以字符串方式保存或传输的话,可以使用Base64编码。
public class RSAUtil { /** 指定加密算法为RSA */ private static String ALGORITHM = "RSA"; /*指定加密模式和填充方式*/ private static String ALGORITHM_MODEL = "RSA/ECB/PKCS1Padding"; /** 指定key的大小,一般为1024,越大安全性越高 */ private static int KEYSIZE = 1024; /** 指定公钥存放文件 */ private static String PUBLIC_KEY_FILE = "PublicKey"; /** 指定私钥存放文件 */ private static String PRIVATE_KEY_FILE = "PrivateKey"; /** * 生成密钥对 */ private static void generateKeyPair() throws Exception { /** RSA算法要求有一个可信任的随机数源 */ SecureRandom sr = new SecureRandom(); /** 为RSA算法创建一个KeyPairGenerator对象 */ KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM); /** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */ kpg.initialize(KEYSIZE, sr); /** 生成密匙对 */ KeyPair kp = kpg.generateKeyPair(); /** 得到公钥 */ Key publicKey = kp.getPublic(); /** 得到私钥 */ Key privateKey = kp.getPrivate(); /** 用对象流将生成的密钥写入文件 */ ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream(PUBLIC_KEY_FILE)); ObjectOutputStream oos2 = new ObjectOutputStream(new FileOutputStream(PRIVATE_KEY_FILE)); oos1.writeObject(publicKey); oos2.writeObject(privateKey); /** 清空缓存,关闭文件输出流 */ oos1.close(); oos2.close(); } /** * 加密方法 source: 源数据 */ public static byte[] encrypt(String source) throws Exception { /** 将文件中的公钥对象读出 */ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(PUBLIC_KEY_FILE)); Key key = (Key) ois.readObject(); ois.close(); /** 得到Cipher对象来实现对源数据的RSA加密 */ Cipher cipher = Cipher.getInstance(ALGORITHM_MODEL); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] b = source.getBytes(); /** 执行加密操作 */ byte[] b1 = cipher.doFinal(b); return b1; } /** * 解密算法 cryptograph:密文 */ public static String decrypt(byte[] cryptograph) throws Exception { /** 将文件中的私钥对象读出 */ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(PRIVATE_KEY_FILE)); Key key = (Key) ois.readObject(); /** 得到Cipher对象对已用公钥加密的数据进行RSA解密 */ Cipher cipher = Cipher.getInstance(ALGORITHM_MODEL); cipher.init(Cipher.DECRYPT_MODE, key); /** 执行解密操作 */ byte[] b = cipher.doFinal(cryptograph); return new String(b); } public static void main(String[] args) throws Exception { generateKeyPair();//生成密钥对 String source = "Hello World!";// 要加密的字符串 byte[] cryptograph = encrypt(source);// 生成的密文 //可以将密文进行base64编码进行传输 System.out.println(new String(Base64.encode(cryptograph))); String target = decrypt(cryptograph);// 解密密文 System.out.println(target); } }
RSA密钥使用Base64编码
要灵活使用肯定不能使用java的序列化保存,对上面的generateKeyPair()方法进行改写。通过密钥生成器生成公钥,私钥后,调用publicKey.getEncoded()和privateKey.getEncoded(),此时它生成的比特编码是有独特格式的(公钥是X.509,私钥是PKCS#8)可以使用publicKey.getFormat(),privateKey.getFormat();进行查看。之后对字节码进行Base64编码就行了。
密钥生成方法
/** * Base64编码 * * @return */ public static Map<String, String> genKeyPair() throws Exception { Map<String, String> ret = new HashMap<>(); SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM); keyPairGenerator.initialize(KEYSIZE, secureRandom); KeyPair keyPair = keyPairGenerator.generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); byte[] pb = publicKey.getEncoded(); BASE64Encoder base64Encoder = new BASE64Encoder(); String pbStr = base64Encoder.encodeBuffer(pb); ret.put("publicKey", pbStr); byte[] pr = privateKey.getEncoded(); String prStr = base64Encoder.encodeBuffer(pr); ret.put("privateKey", prStr); System.out.println("====" + publicKey.toString() + "---" + pr); return ret; }
恢复密钥方法,使用各自不同的编码形式恢复
/** * 从base64编码的公钥恢复公钥 * * @param pbBase64 * @return */ public static PublicKey getPublicKey(String pbBase64) throws Exception { byte[] pb = (new BASE64Decoder()).decodeBuffer(pbBase64); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pb); KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); return keyFactory.generatePublic(keySpec); } /** * 从base64编码的私钥恢复私钥 * * @param prBase64 * @return */ public PrivateKey getPrivateKey(String prBase64) throws Exception { byte[] pr = (new BASE64Decoder()).decodeBuffer(prBase64); PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(pr); KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); return keyFactory.generatePrivate(encodedKeySpec); }
Base64
通常用于把二进制数据编码为可写的字符形式的数据,对数据内容进行编码来适合传输(可以对img图像编码用于传输)。这是一种可逆的编码方式。编码后的数据是一个字符串,其中包含的字符为:A-Z、a-z、0-9、+、/,共64个字符(26 + 26 + 10 + 1 + 1 = 64,其实是65个字符,“=”是填充字符。Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。原文的字节最后不够3个的地方用0来补足,转换时Base64编码用=号来代替。这就是为什么有些Base64编码会以一个或两个等号结束的原因,中间是不可能出现等号的,但等号最多只有两个。其实不用"="也不耽误解码,之所以用"=",可能是考虑到多段编码后的Base64字符串拼起来也不会引起混淆。)
Base64编码是从二进制到字符的过程,像一些中文字符用不同的编码转为二进制时,产生的二进制是不一样的,最终产生的Base64字符也不一样。例如"上网"对应utf-8格式的Base64编码是"5LiK572R", 对应GB2312格式的Base64编码是"yc/N+A=="
URL的编码
url一般使用的都是英文、数字和某些符号,而对于特殊符号,中文等这些是不允许使用的。因此要在url请求中加入特殊符号,中文等就需要对它们进行编码。http请求时,url部分是必须编码的,get的请求字段可以不进行url编码。比如
http://www.baidu.com/中文?wd=国际
“中文”必须进行url编码,“国际”可以不用。
那url编码到底是怎么进行编码的呢?
都是在16进制前面加上‘%’表示。对于一些字符使用的是”%xx”,而对于中文,就是多个”%xx%xx%xx”,xx的数字有编码的16进制决定(没有指定字符编码(utf8),则使用默认编码),然后每一字节前面加”%”。
Android 中提供的URL编码解码方法。
String d = URLEncoder.encode('中文',"utf8");
String f = URLDecoder.decode("%20");
参考:
https://blog.csdn.net/u013565368/article/details/53081195?_t=t
https://www.cnblogs.com/sochishun/p/7028056.html
浙公网安备 33010602011771号