目录
前言:
我感觉做技术就应该去知道各种细节,还有原理。
多去想一些问题,总有人说我问题太多,
有些问题很奇怪,同事也都不懂。
但是我觉得有问题就有答案,还是要弄明白的。
而且在想一个问题的时候,往往我能想到好多延伸的问题。
这些问题呢,面试也不会问,而且我看了也不一定就记住了。
但是我还是想要去知道。
一个喜欢QA的工程师。
以后如果自己有足够的能力,一定要去写书,0 0貌似做做公众号也成。
活个明白。
1.base64都可以加密什么?
base并非只是用于加密图片的。
1.1 为什么图片用base64传输?
之前一直没有去思索这个问题,因为觉得好像是一种常识一般。
Q:为什么传输图片是常用base64字符串转码,而不是直接传输byte[]?
首先 1byte = 8 bit ,如果全部用byte[]数组会很长,所以每6个字节对应一个新的字符。
这样的目的是为了精简传输。
另外做不严格的加密用,就是常见的迅雷的磁力链接都是base64的,加密比较快,而且可以恢复。
英文字母:
·字节数 : 1;编码:GB2312字节数 : 1;编码:GBK
字节数 : 1;编码:GB18030
字节数 : 1;编码:ISO-8859-1
字节数 : 1;编码:UTF-8
字节数 : 4;编码:UTF-16
字节数 : 2;编码:UTF-16BE
字节数 : 2;编码:UTF-16LE
中文汉字:
字节数 : 2;编码:GB2312字节数 : 2;编码:GBK
字节数 : 2;编码:GB18030
字节数 : 1;编码:ISO-8859-1
字节数 : 3;编码:UTF-8
字节数 : 4;编码:UTF-16
字节数 : 2;编码:UTF-16BE
字节数 : 2;编码:UTF-16LE
一般我们用的UTF-8 都是中文占三个字节,英文是占一个的~
1.2 base64的原理之类?
1.2 这部分来自于:
版权声明:本文为CSDN博主「二师兄-公众号-程序新视界」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wo541075754/article/details/81734770
1.2.1 base64的由来?
目前Base64已经成为网络上常见的传输8Bit字节代码的编码方式之一。在做支付系统时,系统之间的报文交互都需要使用Base64对明文进行转码,然后再进行签名或加密,之后再进行(或再次Base64)传输。那么,Base64到底起到什么作用呢?
在参数传输的过程中经常遇到的一种情况:使用全英文的没问题,但一旦涉及到中文就会出现乱码情况。与此类似,网络上传输的字符并不全是可打印的字符,比如二进制文件、图片等。Base64的出现就是为了解决此问题,它是基于64个可打印的字符来表示二进制的数据的一种方法。
电子邮件刚问世的时候,只能传输英文,但后来随着用户的增加,中文、日文等文字的用户也有需求,但这些字符并不能被服务器或网关有效处理,因此Base64就登场了。随之,Base64在URL、Cookie、网页传输少量二进制文件中也有相应的使用。
图片对应的文件流对应的也是byte[]数组,所以图片转成base64是都经过了byte[] 数组对应的?
1.2.2 编码原理
Base64的原理比较简单,每当我们使用Base64时都会先定义一个类似这样的数组:
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
上面就是Base64的索引表,字符选用了"A-Z、a-z、0-9、+、/" 64个可打印字符,这是标准的Base64协议规定。在日常使用中我们还会看到“=”或“==”号出现在Base64的编码结果中,“=”在此是作为填充字符出现,后面会讲到。
Q: =号是什么?
Q:==号又是什么?
后面会有解答
1.2.3 具体的转换步骤
第一步,将待转换的字符串每三个字节分为一组,每个字节占8bit,那么共有24个二进制位。
第二步,将上面的24个二进制位每6个一组,共分为4组。
第三步,在每组前面添加两个0,每组由6个变为8个二进制位,总共32个二进制位,即四个字节。
第四步,根据Base64编码对照表(见下图)获得对应的值。
总结: 6个bit为一组,然后三个字节变成四个字节,因为每个字节前面的bit 都填了两个0.
然后找到base64编码对应的表
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w
15 P 32 g 49 x
16 Q 33 h 50 y
1.2.4 可能面试会需要知道的
Base64字符表中的字符原本用6个bit就可以表示,现在前面添加2个0,变为8个bit,会造成一定的浪费。因此,Base64编码之后的文本,要比原文大约三分之一。
为什么使用3个字节一组呢?因为6和8的最小公倍数为24,三个字节正好24个二进制位,每6个bit位一组,恰好能够分为4组。
因为6和8的最小公倍数为24.所以文本用base64来表示的话,其实会更大一点。
1.2.5 位数不足情况
上面是按照三个字节来举例说明的,如果字节数不足三个,那么该如何处理?
两个字节:两个字节共16个二进制位,依旧按照规则进行分组。此时总共16个二进制位,每6个一组,则第三组缺少2位,用0补齐,得到三个Base64编码,第四组完全没有数据则用“=”补上。因此,上图中“BC”转换之后为“QKM=”;
一个字节:一个字节共8个二进制位,依旧按照规则进行分组。此时共8个二进制位,每6个一组,则第二组缺少4位,用0补齐,得到两个Base64编码,而后面两组没有对应数据,都用“=”补上。因此,上图中“A”转换之后为“QQ==”;
不够的用0补,如果不到四个字节,用==来补充。
所以base64中的=号就这么出现的
1.2.6 注意事项
大多数编码都是由字符串转化成二进制的过程,而Base64的编码则是从二进制转换为字符串。与常规恰恰相反,
Base64编码主要用在传输、存储、表示二进制领域,不能算得上加密,只是无法直接看到明文。也可以通过打乱Base64编码来进行加密。
中文有多种编码(比如:utf-8、gb2312、gbk等),不同编码对应Base64编码结果都不一样。
2. 图片文件转base64的代码分析
public static String imageToBase64(File file) {
byte[] data = null;
try {
InputStream in = new FileInputStream(file);
data = new byte[in.available()];
in.read(data);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
//不应该用类的实例访问静态资源,已更改
return new String(Base64.encode(data));
}
读取图片文件,所以文件流都是可以转成byte数组的,然后就可以使用base64的转码工具进行转码
3. base64的生成签名的一种小小思想
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* @Description : SignUtils
* @Date : 2019/9/19
* @Author :
*/
public class SignUtils {
public String generateSignature(String accessId, String base64Key, long timestamp) {
String accessKey = new String(Base64.getDecoder().decode(base64Key.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
String signKey = accessId + "-" + timestamp;
try {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(accessKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(secretKey);
return Base64.getEncoder().encodeToString(hmacSha256.doFinal(signKey.getBytes(StandardCharsets.UTF_8)));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
//这边应该使用log,应避免sout
System.out.println(e.getMessage());
return null;
}
}
}
accessId 是提前给客户生成好的。
然后,accessKey是使用base64加密过的key。
不过看完base64的原理之后,base都是通过byte数组来进行加密解密的。
所以这个加密是先通过Mac和 SecretKeySpec 来进行第一层加密,然后在对其进行第二层加密。
3.1 Mac类与SecretKeySpec 类的简单介绍
首先,看到这两个类的时候。
第一个时间也是点进去源码观察。
所属
Javax.crypto
加密操作 提供类和接口
3.2 Mac类
这部分参考+ 转载:https://blog.csdn.net/mn960mn/article/details/78174234
消息摘要(数字摘要),它是把一个文本/文件 通过摘要函数(hash函数)计算出一个结果。然后把文本/文件和摘要结果一同发给接受者
接受者接收到文件之后,也进行摘要,把两个摘要结果进行对比。如果一致就说明文本/文件和摘要是一致的
但是,这里有个问题,假设A把文件和摘要发给B,中途被C截获了。C把文件改了,同时把改后的文件进行摘要。然后把改后的文件和重新生成的摘要发给B。
B收到结果之后,进行摘要,对比发现,是一致的。但是此时文件是被篡改过的,B也不知道。接收方并不能察觉到数据被篡改。
所以说,普通的消息摘要不能验证身份和防篡改
为了解决这个问题,我们可以使用MAC(消息认证码(带密钥的hash函数))去解决
MAC,全称 Message Authentication Code,也称为消息认证码(带密钥的Hash函数),通信实体双方使用的一种验证机制,保证消息数据完整性的一种工具(防止被截获)
在发送数据之前,发送方首先使用通信双方协商好的散列函数计算其摘要值。在双方共享的会话密钥作用下,由摘要值获得消息验证码。之后,它和数据一起被发送。接收方收到报文后,首先利用会话密钥还原摘要值,同时利用散列函数在本地计算所收到数据的摘要值,并将这两个数据进行比对。若两者相等,则报文通过认证。
说白了就是计算摘要的时候,需要一个秘钥key,没有秘钥key就无法计算
注意:相同的消息,不同的key,摘要结果不同。
这也就是上面代码中的思想
Mac.getInstance支持的算法有:HmacMD5、HmacSHA1、HmacSHA256等等
全部支持的算法见官方文档:
https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Mac
3.2.1 代码示例
PS:
SecretKeySpec secretKey = new SecretKeySpec(accessKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(secretKey);
这个SecretKeySpec 是Key类的一种子类,所以Mac类需要使用init来初始化一个消息认证码。
这边的accessKey使用的是一个base64的key进行解码的一串字符串。
然后看下面的代码:
package com.dk.learndemo;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Key;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
/**
* @Description : MacTest
* @Date : 2019/9/20
* @Author :
*/
public class MacTest {
//秘钥(必须要是通信双方共享的)
static final String STR_KEY = "266f5fe18e714688a083df4ca9f78064";
/**
* 其中,Mac.getInstance支持的算法有:HmacMD5、HmacSHA1、HmacSHA256等等
* 全部支持的算法见官方文档:
* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Mac
*/
public static byte[] mac(String algorithm, Key key, byte[] data) throws Exception {
Mac mac = Mac.getInstance(algorithm);
//这里是关键,需要一个key(这里就是和普通的消息摘要的区别点)
mac.init(key);
byte[] result = mac.doFinal(data);
return result;
}
public static void main(String[] args) throws Exception {
byte[] data = Files.readAllBytes(Paths.get("C:\\Users\\DK\\Downloads/apache-tomcat-8.5.45.tar.gz"));
//这边的字符串填上或者不填有什么区别么 对于解密的时候有什么影响
Key key = new SecretKeySpec(STR_KEY.getBytes(), "");
//结果证明这边的第二个参数随便些什么都不会有影响。
Key key2 = new SecretKeySpec(STR_KEY.getBytes(), "111");
//使用MD5算法计算摘要
byte[] md5Digest = mac("HmacMD5", key, data);
//使用SHA256算法计算摘要
byte[] shaDigest = mac("HmacSHA256", key, data);
//这边是用Key2来计算摘要
byte[] shaDigest2 = mac("HmacSHA256", key2, data);
//把摘要后的结果转换成十六进制的字符串(也可以使用Base64进行编码)
System.out.println(Hex.encodeHexString(md5Digest));
System.out.println(Hex.encodeHexString(shaDigest));
System.out.println(Hex.encodeHexString(shaDigest2));
}
}
结果:
47011b8e70c69a77d4332dc62a6a2269
5b8c523b8f1c8302cb73eadb970217a1aae234cca96d362a90ece77c2cf41129
5b8c523b8f1c8302cb73eadb970217a1aae234cca96d362a90ece77c2cf41129
Key key2 = new SecretKeySpec(STR_KEY.getBytes(), "111");
这个字符串填什么并不影响结果,影响结果的是使用什么算法来计算摘要。
然后,我们可以使用OpenSSL,加上上面使用的秘钥key,计算摘要
对比结果,发现是一致的。
3.2.2 Mac支持的算法
Security.getAlgorithms("Mac").forEach(System.out::println);
结果:
PBEWITHHMACSHA512
PBEWITHHMACSHA224
PBEWITHHMACSHA256
HMACSHA384
PBEWITHHMACSHA384
HMACSHA256
HMACPBESHA1
HMACSHA224
HMACMD5
PBEWITHHMACSHA1
SSLMACSHA1
HMACSHA512
SSLMACMD5
HMACSHA1
3.3 SecretKeySpec
关于这个类,个人理解这个类是Key类的一个子类
但是实际上看源码中,这个类的继承关系是:
public class SecretKeySpec implements KeySpec, SecretKey {
}
具体的方法:
3.1 实现的接口类KeySpec
/**
*
* <P> This interface contains no methods or constants. Its only purpose
* is to group (and provide type safety for) all key specifications.
* All key specifications must implement this interface.
*
* @author Jan Luehe
*
*
* @see java.security.Key
* @see java.security.KeyFactory
* @see EncodedKeySpec
* @see X509EncodedKeySpec
* @see PKCS8EncodedKeySpec
* @see DSAPrivateKeySpec
* @see DSAPublicKeySpec
*
* @since 1.2
*/
public interface KeySpec { }
此接口不包含任何方法或常量。它的唯一目的对所有关键规范进行分组(并提供类型安全)。所有关键规范都必须实现此接口。
3.2 SecretKey
package javax.crypto;
import java.security.Key;
import javax.security.auth.Destroyable;
public interface SecretKey extends Key, Destroyable {
long serialVersionUID = -4795878709595146952L;
}
这边是接口继承了接口。
Key也是一个接口
3.3 Key
3.3.1 秘钥的概念
密钥分两种:对称密钥和非对称密钥。
非对称密钥里又包含公开密钥和私有密钥。
与密钥相关的还有一个概念是证书。证书主要用于鉴别密钥,通常将公开密钥放到证书里传输。
Java的安全体系里,密钥是通过JCE算法包实现的。操作密钥的引擎包含两部分:密钥生成器和密钥工厂。密钥生成器可以创建密钥,而密钥工厂将其进行包装展示到外部。所以对于编写程序来说,创建密钥包括两个步骤:1,用密钥生成器产生密钥;2,用密钥工厂将其输出为一个密钥规范或者一组字节码。
3.3.2 java的实现
Java里将密钥封装了一个接口——Key。非对称密钥有PublicKey和PrivateKey,均实现了该接口。从之前的“安全提供者框架”中的输出结果可以看到,不同的安全提供者提供了很多密钥生成算法,比较典型的是Sun的DSA和RSA以及JCE的Diffie-Hellman算法。
具体内容看:https://www.cnblogs.com/jpfss/p/8574341.html