赞
踩
目录
我感觉做技术就应该去知道各种细节,还有原理。
多去想一些问题,总有人说我问题太多,
有些问题很奇怪,同事也都不懂。
但是我觉得有问题就有答案,还是要弄明白的。
而且在想一个问题的时候,往往我能想到好多延伸的问题。
这些问题呢,面试也不会问,而且我看了也不一定就记住了。
但是我还是想要去知道。
一个喜欢QA的工程师。
以后如果自己有足够的能力,一定要去写书,0 0貌似做做公众号也成。
活个明白。
base并非只是用于加密图片的。
之前一直没有去思索这个问题,因为觉得好像是一种常识一般。
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 这部分来自于:
版权声明:本文为CSDN博主「二师兄-公众号-程序新视界」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wo541075754/article/details/81734770
目前Base64已经成为网络上常见的传输8Bit字节代码的编码方式之一。在做支付系统时,系统之间的报文交互都需要使用Base64对明文进行转码,然后再进行签名或加密,之后再进行(或再次Base64)传输。那么,Base64到底起到什么作用呢?
在参数传输的过程中经常遇到的一种情况:使用全英文的没问题,但一旦涉及到中文就会出现乱码情况。与此类似,网络上传输的字符并不全是可打印的字符,比如二进制文件、图片等。Base64的出现就是为了解决此问题,它是基于64个可打印的字符来表示二进制的数据的一种方法。
电子邮件刚问世的时候,只能传输英文,但后来随着用户的增加,中文、日文等文字的用户也有需求,但这些字符并不能被服务器或网关有效处理,因此Base64就登场了。随之,Base64在URL、Cookie、网页传输少量二进制文件中也有相应的使用。
图片对应的文件流对应的也是byte[]数组,所以图片转成base64是都经过了byte[] 数组对应的?
Base64的原理比较简单,每当我们使用Base64时都会先定义一个类似这样的数组:
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
上面就是Base64的索引表,字符选用了"A-Z、a-z、0-9、+、/" 64个可打印字符,这是标准的Base64协议规定。在日常使用中我们还会看到“=”或“==”号出现在Base64的编码结果中,“=”在此是作为填充字符出现,后面会讲到。
Q: =号是什么?
Q:==号又是什么?
后面会有解答
第一步,将待转换的字符串每三个字节分为一组,每个字节占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
Base64字符表中的字符原本用6个bit就可以表示,现在前面添加2个0,变为8个bit,会造成一定的浪费。因此,Base64编码之后的文本,要比原文大约三分之一。
为什么使用3个字节一组呢?因为6和8的最小公倍数为24,三个字节正好24个二进制位,每6个bit位一组,恰好能够分为4组。
因为6和8的最小公倍数为24.所以文本用base64来表示的话,其实会更大一点。
上面是按照三个字节来举例说明的,如果字节数不足三个,那么该如何处理?
两个字节:两个字节共16个二进制位,依旧按照规则进行分组。此时总共16个二进制位,每6个一组,则第三组缺少2位,用0补齐,得到三个Base64编码,第四组完全没有数据则用“=”补上。因此,上图中“BC”转换之后为“QKM=”;
一个字节:一个字节共8个二进制位,依旧按照规则进行分组。此时共8个二进制位,每6个一组,则第二组缺少4位,用0补齐,得到两个Base64编码,而后面两组没有对应数据,都用“=”补上。因此,上图中“A”转换之后为“QQ==”;
不够的用0补,如果不到四个字节,用==来补充。
所以base64中的=号就这么出现的
大多数编码都是由字符串转化成二进制的过程,而Base64的编码则是从二进制转换为字符串。与常规恰恰相反,
Base64编码主要用在传输、存储、表示二进制领域,不能算得上加密,只是无法直接看到明文。也可以通过打乱Base64编码来进行加密。
中文有多种编码(比如:utf-8、gb2312、gbk等),不同编码对应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的转码工具进行转码
- 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 来进行第一层加密,然后在对其进行第二层加密。
首先,看到这两个类的时候。
第一个时间也是点进去源码观察。
所属
Javax.crypto
加密操作 提供类和接口
这部分参考+ 转载: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
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,计算摘要
对比结果,发现是一致的。
Security.getAlgorithms("Mac").forEach(System.out::println);
结果:
PBEWITHHMACSHA512
PBEWITHHMACSHA224
PBEWITHHMACSHA256
HMACSHA384
PBEWITHHMACSHA384
HMACSHA256
HMACPBESHA1
HMACSHA224
HMACMD5
PBEWITHHMACSHA1
SSLMACSHA1
HMACSHA512
SSLMACMD5
HMACSHA1
关于这个类,个人理解这个类是Key类的一个子类
但是实际上看源码中,这个类的继承关系是:
public class SecretKeySpec implements KeySpec, SecretKey {
}
具体的方法:
- /**
- *
- * <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 { }
此接口不包含任何方法或常量。它的唯一目的对所有关键规范进行分组(并提供类型安全)。所有关键规范都必须实现此接口。
- package javax.crypto;
-
- import java.security.Key;
- import javax.security.auth.Destroyable;
-
- public interface SecretKey extends Key, Destroyable {
- long serialVersionUID = -4795878709595146952L;
- }
这边是接口继承了接口。
Key也是一个接口
密钥分两种:对称密钥和非对称密钥。
非对称密钥里又包含公开密钥和私有密钥。
与密钥相关的还有一个概念是证书。证书主要用于鉴别密钥,通常将公开密钥放到证书里传输。
Java的安全体系里,密钥是通过JCE算法包实现的。操作密钥的引擎包含两部分:密钥生成器和密钥工厂。密钥生成器可以创建密钥,而密钥工厂将其进行包装展示到外部。所以对于编写程序来说,创建密钥包括两个步骤:1,用密钥生成器产生密钥;2,用密钥工厂将其输出为一个密钥规范或者一组字节码。
Java里将密钥封装了一个接口——Key。非对称密钥有PublicKey和PrivateKey,均实现了该接口。从之前的“安全提供者框架”中的输出结果可以看到,不同的安全提供者提供了很多密钥生成算法,比较典型的是Sun的DSA和RSA以及JCE的Diffie-Hellman算法。
具体内容看:https://www.cnblogs.com/jpfss/p/8574341.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。