知道对方id和密码能看到所有信息吗id,密码,加密结果能推导出用的什么加密算法吗?

Base64从本质来说,其实并不是加密算法,只是一种编码方式而已,Base64的"加解密"也不叫加密解密,而是编码解码,但是现在一般都把它归为加密算法。一、Base64简介Base64编码将二进制数据编码为可现实的字母和数字,用于传送图形、声音和传真等非文本数据,常用于MIME电子邮件格式中。其使用含有65个字符和ASCLL字符集(第65个字符为"=",用于对字符串进行特殊处理),并用6个进制位标识一个可显示字符。
为什么会有Base64编码呢?  因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就不能通过邮件传送。这样用途就受到了很大的限制,比如图片二进制流的每个字节不可能全部是可见字符,所以就传送不了。最好的方法就是在不改变传统协议的情况下,做一种扩展方案来支持二进制文件的传送。把不可打印的字符也能用可打印字符来表示,问题就解决了。Base64编码应运而生,Base64就是一种基于64个可打印字符来表示二进制数据的表示方法。
二、Base64实现原理它是用64个可打印字符表示二进制所有数据方法。由于2的6次方等于64,所以可以用每6个位元为一个单元,对应某个可打印字符。我们知道三个字节有24个位元,就可以刚好对应于4个Base64单元,即3个字节需要用4个Base64的可打印字符来表示。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9 ,这样共有62个字符,此外两个可打印符号在不同的系统中一般有所不同。但是,我们经常所说的Base64另外2个字符是:“+/”。这64个字符,所对应表如下。转换的时候,将三个byte的数据,先后放入一个24bit的缓冲区中,先来的byte占高位。数据不足3byte的话,于缓冲区中剩下的bit用0补足。然后,每次取出6个bit,按照其值选择ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。如果最后剩下两个输入数据,在编码结果后加1个“=”;如果最后剩下一个输入数据,编码结果后加2个“=”;如果没有剩下任何数据,就什么都不要加,这样才可以保证资料还原的正确性。编码后的数据比原始数据略长,为原来的4/3。无论什么样的字符都会全部被编码。举个例子:
比如说有一封邮件,我们想要对其使用Base64进行编码。怎么办呢?基本步骤如下:
(1)对邮件的数据进行切分,每三个字节一组,一共24个bit。
(2)对切分后的数据重组,24个bit重组为4组,每组6个bit。
(3)对重组后的数据处理,每组最前面添加两个“0”,构成每组8个bit。此时一共32个bit。
(4)根据Base64编码表,获取相应的编码值。
此时一封完整的邮件,被切分重组处理之后就变成了Base64编码了。基本原理其实很简单。不过你不理解也没关系,我们直接上个实例来解释一下。
三、Base64的安全性因为他都不是一种加密算法,编码解码的方法,完全公开,所以安全性上比较差,但是人家主要也不是专门干这个的。写在最后:这篇文章写的不错:https://baijiahao.baidu.com/s?id=1644892102150918183&wfr=spider&for=pc
SHA256算法sha256与md5一样是散列算法,不是加密算法,不存在解密的问题,因此是不可逆的,可以通过key+password,对密码进行加密,在后台进行比对,安全性比md5高一点,加密后生成的密文为64位,而md5为32位;此外还可以使用sha512安全性相对更高一些,密文为128位。前端使用vue引入npm install js-sha256下载地址为:https://cdnjs.cloudflare.com/ajax/libs/js-sha256/0.9.0/sha256.js 或 https://cdnjs.cloudflare.com/ajax/libs/js-sha256/0.9.0/sha256.min.js// If you use node.js, you should require the module first: // var sha256 = require('js-sha256'); // or TypeScript import { sha256, sha224 } from 'js-sha256'; function randomString(e) { e = e 32; //e为随机码位数 var t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"; a = t.length; n = ""; for (i = 0; i < e; i++){ n += t.charAt(Math.floor(Math.random() * a)); } return n; } var key=randomString(16); //加密传入后台 var enctyptData = sha256(key+password); //前台页面输入的密码 // var enctyptData = sha256.hex(key+password); 支持中英文 数组 sha256()和sha256.hex()密文一致 java后端使用String decryptData = DigestUtils.sha256Hex(key+password); //从前端传入的密码,key为解密后的key值 以上可看出在使用sha256算法进行加密时,前后台同时需要key值,为了保证前后台在传输key的过程中安全性更高,使得key值不被泄露,因此可以使用ras加密算法对key值进行加密。RSA算法RSA算法是一种非对称的加密算法,通过生成一对私钥密钥进行加解密,后台将私钥进行存储在session中,将公钥传到前台进行关键值加密,将加密之后的密文传往后台,通过使用私钥进行解密,因此rsa算法是可逆的,通常用于对key值进行加解密,与AES算法结合使用,保证系统的安全性。前台使用vue引入 npm install jsencrypt或下载地址(jsencrypt.js / jsencrypt.min.js)为:https://github.com/travist/jsencrypt/blob/master/bin/jsencrypt.js JavaScript RSA Encryption






后台使用生成私钥密钥对+加解密数据package RSA; import java.io.ByteArrayOutputStream; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator;

原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。
简介前面在密码学入门一文中讲解了各种常见的密码学概念、算法与运用场景,但没有介绍过代码,因此,为作补充,这一篇将会介绍使用Java语言如何实现使用这些算法,并介绍一下使用过程中可能遇到的坑。Java加密体系JCAJava抽象了一套密码算法框架JCA(Java Cryptography Architecture),在此框架中定义了一套接口与类,以规范Java平台密码算法的实现,而Sun,SunRsaSign,SunJCE这些则是一个个JCA的实现Provider,以实现具体的密码算法,这有点像List与ArrayList、LinkedList的关系一样,Java开发者只需要使用JCA即可,而不用管具体是怎么实现的。JCA里定义了一系列类,如Cipher、MessageDigest、MAC、Signature等,分别用于实现加密、密码学哈希、认证码、数字签名等算法,一起来看看吧!对称加密对称加密算法,使用Cipher类即可,以广泛使用的AES为例,如下:public byte[] encrypt(byte[] data, Key key) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = SecureRandoms.randBytes(cipher.getBlockSize());
//初始化密钥与加密参数iv
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
//加密
byte[] encryptBytes = cipher.doFinal(data);
//将iv与密文拼在一起
ByteArrayOutputStream baos = new ByteArrayOutputStream(iv.length + encryptBytes.length);
baos.write(iv);
baos.write(encryptBytes);
return baos.toByteArray();
} catch (Exception e) {
return ExceptionUtils.rethrow(e);
}
}
public byte[] decrypt(byte[] data, Key key) {try {Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");//获取密文前面的ivIvParameterSpec ivSpec = new IvParameterSpec(data, 0, cipher.getBlockSize());cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);//解密iv后面的密文return cipher.doFinal(data, cipher.getBlockSize(), data.length - cipher.getBlockSize());} catch (Exception e) {return ExceptionUtils.rethrow(e);}}复制代码如上,对称加密主要使用Cipher,不管是AES还是DES,Cipher.getInstance()传入不同的算法名称即可,这里的Key参数就是加密时使用的密钥,稍后会介绍它是怎么来的,暂时先忽略它。另外,为了使得每次加密出来的密文不同,我使用了随机的iv向量,并将iv向量拼接在了密文前面。
注:如果某个算法名称,如上面的AES/CBC/PKCS5Padding,你不知道它在JCA中的标准名称是什么,可以到 docs.oracle.com/en/java/jav… 中查询即可。
非对称加密非对称加密同样是使用Cipher类,只是传入的密钥对象不同,以RSA算法为例,如下:public byte[] encryptByPublicKey(byte[] data, PublicKey publicKey){
try{
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}catch (Exception e) {
throw Errors.toRuntimeException(e);
}
}
public byte[] decryptByPrivateKey(byte[] data, PrivateKey privateKey){try{Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, privateKey);return cipher.doFinal(data);}catch (Exception e) {throw Errors.toRuntimeException(e);}}复制代码一般来说应使用公钥加密,私钥解密,但其实反过来也是可以的,这里的PublicKey与PrivateKey也先忽略,后面会介绍它怎么来的。密码学哈希密码学哈希算法包括MD5、SHA1、SHA256等,在JCA中都使用MessageDigest类即可,如下:public static String sha256(byte[] bytes) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(bytes);
return Hex.encodeHexString(digest.digest());
}
复制代码消息认证码消息认证码使用Mac类实现,以常见的HMAC搭配SHA256为例,如下:public byte[] digest(byte[] data, Key key) throws InvalidKeyException, NoSuchAlgorithmException{
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
return mac.doFinal(data);
}
复制代码数字签名数字签名使用Signature类实现,以RSA搭配SHA256为例,如下:public byte[] sign(byte[] data, PrivateKey privateKey) {
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
} catch (Exception e) {
return ExceptionUtils.rethrow(e);
}
}
public boolean verify(byte[] data, PublicKey publicKey, byte[] sign) {try {Signature signature = Signature.getInstance("SHA256withRSA");signature.initVerify(publicKey);signature.update(data);return signature.verify(sign);} catch (Exception e) {return ExceptionUtils.rethrow(e);}}复制代码密钥协商算法在JCA中,使用KeyAgreement来调用密钥协商算法,以ECDH协商算法为例,如下:public static void testEcdh() {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
keyGen.initialize(ecSpec);
// A生成自己的私密信息
KeyPair keyPairA = keyGen.generateKeyPair();
KeyAgreement kaA = KeyAgreement.getInstance("ECDH");
kaA.init(keyPairA.getPrivate());
// B生成自己的私密信息
KeyPair keyPairB = keyGen.generateKeyPair();
KeyAgreement kaB = KeyAgreement.getInstance("ECDH");
kaB.init(keyPairB.getPrivate());
<span class="hljs-comment">// B收到A发送过来的公用信息,计算出对称密钥</span>
kaB.doPhase(keyPairA.getPublic(), <span class="hljs-literal">true</span>);
<span class="hljs-type">byte</span>[] kBA = kaB.generateSecret();
<span class="hljs-comment">// A收到B发送过来的公开信息,计算对对称密钥</span>
kaA.doPhase(keyPairB.getPublic(), <span class="hljs-literal">true</span>);
<span class="hljs-type">byte</span>[] kAB = kaA.generateSecret();
Assert.isTrue(Arrays.equals(kBA, kAB), <span class="hljs-string">"协商的对称密钥不一致"</span>);
}复制代码基于口令加密PBE通常,对称加密算法需要使用128位字节的密钥,但这么长的密钥用户是记不住的,用户容易记住的是口令,也即password,但与密钥相比,口令有如下弱点:口令通常较短,这使得直接使用口令加密的强度较差。
口令随机性较差,因为用户一般使用较容易记住的东西来生成口令。
为了使得用户能直接使用口令加密,又能最大程度避免口令的弱点,于是PBE(Password Based Encryption)算法诞生,思路如下:既然密码算法需要密钥,那在加解密前,先使用口令生成密钥,然后再使用此密钥去加解密。
为了弥补口令随机性较差的问题,生成密钥时使用随机盐来混淆口令来产生准密钥,再使用散列函数对准密钥进行多次散列迭代,以生成最终的密钥。
因此,使用PBE算法进行加解密时,除了要提供口令外,还需要提供随机盐(salt)与迭代次数(iteratorCount),如下:public static byte[] encrypt(byte[] plainBytes, String password, byte[] salt, int iteratorCount) {
try {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES").generateSecret(keySpec);
<span class="hljs-type">Cipher</span> <span class="hljs-variable">cipher</span> <span class="hljs-operator">=</span> Cipher.getInstance(<span class="hljs-string">"PBEWithMD5AndTripleDES"</span>);
cipher.init(Cipher.ENCRYPT_MODE, key, <span class="hljs-keyword">new</span> <span class="hljs-title class_">PBEParameterSpec</span>(salt, iteratorCount));
<span class="hljs-type">byte</span>[] encryptBytes = cipher.doFinal(plainBytes);
<span class="hljs-type">byte</span>[] iv = cipher.getIV();
<span class="hljs-type">ByteArrayOutputStream</span> <span class="hljs-variable">baos</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ByteArrayOutputStream</span>(iv.length + encryptBytes.length);
baos.write(iv);
baos.write(encryptBytes);
<span class="hljs-keyword">return</span> baos.toByteArray();
} <span class="hljs-keyword">catch</span> (Exception e) {
<span class="hljs-keyword">throw</span> Errors.toRuntimeException(e);
}
}
public static byte[] decrypt(byte[] secretBytes, String password, byte[] salt, int iteratorCount) {try {PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES").generateSecret(keySpec);
<span class="hljs-type">Cipher</span> <span class="hljs-variable">cipher</span> <span class="hljs-operator">=</span> Cipher.getInstance(<span class="hljs-string">"PBEWithMD5AndTripleDES"</span>);
<span class="hljs-type">IvParameterSpec</span> <span class="hljs-variable">ivParameterSpec</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IvParameterSpec</span>(secretBytes, <span class="hljs-number">0</span>, cipher.getBlockSize());
cipher.init(Cipher.DECRYPT_MODE, key, <span class="hljs-keyword">new</span> <span class="hljs-title class_">PBEParameterSpec</span>(salt, iteratorCount, ivParameterSpec));
<span class="hljs-keyword">return</span> cipher.doFinal(secretBytes, cipher.getBlockSize(), secretBytes.length - cipher.getBlockSize());
} <span class="hljs-keyword">catch</span> (Exception e) {
<span class="hljs-keyword">throw</span> Errors.toRuntimeException(e);
}
}
public static void main(String[] args) throws Exception {byte[] content = "hello".getBytes(StandardCharsets.UTF_8);byte[] salt = Base64.decode("QBadPOP6/JM=");String password = "password";byte[] encoded = encrypt(content, password, salt, 1000);System.out.println("密文:" + Base64.encode(encoded));byte[] plainBytes = decrypt(encoded, password, salt, 1000);System.out.println("明文:" + new String(plainBytes, StandardCharsets.UTF_8));}复制代码注意,虽然使用PBE加解密数据,都需要使用相同的password、salt、iteratorCount,但这里面只有password是需要保密的,salt与iteratorCount不需要,可以保存在数据库中,比如每个用户注册时给他生成一个随机盐。到此,JCA密码算法就介绍完了,来回顾一下:整体来说,JCA对密码算法相关的类设计与封装还是非常清晰简单的!但使用密码算法时,依赖SecretKey、PublicKey、PrivateKey对象提供密钥信息,那这些密钥对象是怎么来的呢?密钥生成与读取密码学随机数密码学随机数算法在安全场景中使用广泛,如:生成对称密钥、盐、iv等,因此相比普通的随机数算法(如线性同余),它需要更高强度的不可预测性,在Java中,使用SecureRandom来生成更安全的随机数,如下:public class SecureRandoms {
public static byte[] randBytes(int len) throws NoSuchAlgorithmException {
byte[] bytes = new byte[len];
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.nextBytes(bytes);
return bytes;
}
}
复制代码SecureRandom使用了更高强度的随机算法,同时会读取机器本身的随机熵值,如/dev/urandom,因此相比普通的Random,它具有更强的随机性,因此,对于需要生成密钥的场景,该用哪个要拧得清。对称密钥在JCA中对称密钥使用SecretKey表示,若要生成一个新的SecretKey,可使用KeyGenerator,如下://生成新的密钥
public static SecretKey genSecretKey() {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(SecureRandom.getInstance("SHA1PRNG"));
SecretKey secretKey = keyGenerator.generateKey();
}
复制代码而如果是从文件中读取密钥的话,则可以借助SecretKeyFactory将其转换为SecretKey,如下://读取密钥
public static SecretKey getSecretKey() {
byte[] keyBytes = readKeyBytes();
String alg = "AES";
SecretKey secretKey = SecretKeyFactory.getInstance(alg).generateSecret(new SecretKeySpec(keyBytes, alg));
}
复制代码非对称密钥在JCA中,对于非对称密钥,公钥使用PublicKey表示,私钥使用PrivateKey表示,若要生成一个新的公私钥对,可使用KeyPairGenerator,如下://生成新的公私钥对
public static void genKeyPair() {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(2048);
KeyPair keyPair = keyPairGen.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
}
复制代码而如果是从文件中读取公私钥的话,一般公钥是X509格式,而私钥是PKCS8格式,分别对应JCA中的X509EncodedKeySpec与PKCS8EncodedKeySpec,如下://读取私钥
public static PrivateKey getPrivateKey() {
byte[] privateKeyBytes = readPrivateKeyBytes();
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(pkcs8EncodedKeySpec);
}
//读取公钥public static PublicKey getPublicKey() {byte[] publicKeyBytes = readPublicKeyBytes();X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec);}复制代码注意,KeyGenerator、KeyPairGenerator与KeyFactory从命名上看起来有点相似,但它们实现的功能是完全不同的,KeyGenerator、KeyPairGenerator用于生成新的密钥,而KeyFactory则用于将KeySpec转换为对应的Key密钥对象。JCA密钥相关类关系一览,如下:常见问题密文无法解密问题有时,在使用密码算法时,会发现别人提供的密文使用正确的密钥却无法解密出来,特别容易发生在跨语言的情况下,如加密方使用的C#语言,而解密方却使用的Java。遇到这种情况,你需要和对方认真确认加密时使用的加密模式、填充模式以及IV等密码参数是否完全一致。如AES算法加密模式有ECB、CBC、CFB、CTR、GCM等,填充模式有PKCS#5, ISO 10126, ANSI X9.23等,以及对方是使用了固定的IV向量还是将IV向量拼在了密文中,这些都需要确认清楚并与对方保持一致才能正确解密。签名失败问题签名失败也是使用密码算法时常见的情况,比如对方生成的MD5值与你生成的MD5不一致,常见有2种原因,如下:1. 使用的字符编码不一致导致密码算法为了通用性,操作对象都是字节数组,而你要签名的对象一般是字符串,因此你需要将字符串转为字节数组之后再做md5运算,如下:调用方:md5(str.getBytes())
服务方:md5(str.getBytes())
看起来两边的代码一模一样,但问题就在getBytes()函数中,getBytes()函数默认会使用操作系统的字符编码将字符串转为字节数组,而中文Windows默认字符编码是GBK,而Linux默认是UTF-8,这就导致当str中有中文时,调用方与服务方获取到的字节数组是不一样的,那生成的MD5值当然也不一样了。因此,强烈推荐在使用getBytes()函数时,传入统一的字符编码,如下:调用方:md5(str.getBytes("UTF-8"))
服务方:md5(str.getBytes("UTF-8"))这样就能有效地避过这个非常隐晦的坑了。
2. json的escape功能导致有些json框架,做json序列化时会默认做一些转义操作,如把&字符转义为\u0026,但如果服务端做json反序列化时没有做反转义,这会导致两边计算的签名值不一样,如下:调用方:md5("&")
服务方:md5("\\u0026")这也是一个非常隐晦的坑,如Gson默认就会有这种行为,可使用new GsonBuilder().disableHtmlEscaping()禁用。
生成与读取证书概念随着对密码学了解的深入,会发现有特别多奇怪的名词出现,让人迷惑不已,如PKCS8、X.509、ASN.1、DER、PEM等,接下来就来澄清下这些名词是什么,以及它们之间的关系。首先,了解3个概念,如下:密钥:包括对称密钥与非对称密钥等。
证书:包含用户或网站的身份信息、公钥,以及CA的签名。
密钥库:用于存储密钥与证书的仓库。
ASN.1语法ASN.1抽象语法标记(Abstract Syntax Notation One),和XML、JSON类似,用于描述对象结构,可以把它看成一种描述语言,简单的示例如下:Report ::= SEQUENCE {
author OCTET STRING,
title OCTET STRING,
body OCTET STRING,
}
复制代码这个语法描述了一个结构体,它包含3个属性author、title、body,且都是字符串类型。DER与PEMDER是ASN.1的一种序列化编码方案,也就是说ASN.1用来描述对象结构,而DER用于将此对象结构编码为可存储的字节数组。PEM(Privacy Enhanced Mail)是一种将二进制数据,以文本形式进行存储或传输的方案,早期主要用于邮件中交换证书,它的文本内容常以-----BEGIN XXX-----开头,并以-----END XXX-----结尾,而中间 Body 部分则为 Base64 编码后的数据,如下是一个证书的PEM样例。以上面证书为例,PEM与DER的关系大概如下:PEM = "-----BEGIN CERTIFICATE-----" + base64(DER) +
"-----END CERTIFICATE-----"
复制代码X.509、PKCS8、PKCS12等X.509、PKCS8、PKCS12等都是公钥密码学标准(PKCS)组织制定的各种密码学规范,该组织使用ASN.1语法为密钥、证书、密钥库等定义了标准的对象结构,常见的如下:X.509规范:用于描述证书与公钥的标准格式。
PKCS7规范:可描述的对象很多,不过一般也是用于描述证书的。
PKCS8规范:用于描述私钥的标准格式。
PKCS12规范:用于描述密钥库的标准格式。
PKCS1规范:用于描述RSA算法及其公私钥的标准格式。
这些规范都有相应的RFC文档,感兴趣的可以前往查看:PEM:https://www.rfc-editor.org/rfc/rfc7468
X.509:https://datatracker.ietf.org/doc/html/rfc5280
PKCS7:https://datatracker.ietf.org/doc/html/rfc2315
PKCS8:https://datatracker.ietf.org/doc/html/rfc8351
PKCS12:https://datatracker.ietf.org/doc/html/rfc7292
PKCS1:https://datatracker.ietf.org/doc/html/rfc8017#appendix-A
复制代码类比一下,如果把ASN.1比作Java,那X.509就是使用Java定义的一个名叫X509的类,这个类里面包含身份信息、公钥信息等相关字段,而DER就是一种Java对象序列化方案,用于将X509这个类的对象序列化为字节数组,字节数组保存为文件后,这个文件就是我们常说的证书或密钥文件。常见证书文件由于PKCS组织并未给证书文件定下标准的文件名后缀,所以证书文件有非常多的后缀名,如下:.der: DER编码的证书,一般是X.509规范的,无法用文本编辑器直接打开
.pem: PEM编码的证书,一般是X.509规范的
.crt: 常见于unix类系统,一般是X.509规范的,可能是DER编码或PEM编码
.cer: 常见于windows系统,一般是X.509规范的,可能是DER编码或PEM编码
.p7b: 常见于windows系统,PKCS7规范证书,可能是DER编码或PEM编码
.pfx:PKCS12规范的密钥库文件,也有取名为.p12的
.jks:java专用的密钥库文件格式,在java技术栈内使用较多,非java一般使用.pfx
证书概念小结生成证书与密钥库openssl命令提供了大量的工具,用以生成密钥、证书与密钥库文件,如下,是一个典型的生成密钥与证书的过程:# 生成pkcs1 rsa私钥
openssl genrsa -out rsa_private_key_pkcs1.key 2048
# 生成pkcs1 rsa公钥
openssl rsa -in rsa_private_key_pkcs1.key -RSAPublicKey_out -out rsa_public_key_pkcs1.key
# 生成证书申请文件cert.csropenssl req -new -key rsa_private_key_pkcs1.key -out cert.csr# 自签名(演示时使用,生产环境一般不用自签证书)
openssl x509 -req -days 365 -in cert.csr -signkey rsa_private_key_pkcs1.key -out cert.crt# ca签名(将证书申请文件提交给ca机构签名)openssl x509 -req -days 365 -in cert.csr -CA ca_cert.crt -CAkey ca_private_key.pem -CAcreateserial -out cert.crt
# 生成p12密钥库文件openssl pkcs12 -export -in cert.crt -inkey rsa_private_key_pkcs1.key -name demo -out keystore.p12复制代码有时别人发来的密钥或证书文件无法读取,也可使用openssl确认一下,如果openssl能读出来,那大概率是自己程序有问题,如果openssl读不出来,那大概率是别人发的文件有问题,如下:# 查看pkcs1 rsa私钥
openssl rsa -in rsa_private_key_pkcs1.key -text -noout
# 查看pkcs1 rsa公钥
openssl rsa -RSAPublicKey_in -in rsa_public_key_pkcs1.key -text -noout
# 查看x.509证书openssl x509 -in cert.crt -text -nocert
# 查看pkcs12密钥库文件openssl pkcs12 -in keystore.p12keytool -v -list -storetype pkcs12 -keystore keystore.p12复制代码由于密钥、证书、密钥库文件,其实都是使用ASN.1语法描述的,所以它们都能按ASN.1语法解析出来,如下:openssl asn1parse -i -inform pem -in cert.crt
复制代码证书格式转换某些情况下,我们需要在不同格式的密钥或证书文件之间转换,也可使用openssl命令来完成。密钥格式转换,如下:# rsa公钥转换为X509公钥
openssl rsa -RSAPublicKey_in -in rsa_public_key_pkcs1.key -pubout -out public_key_x509.key
# rsa私钥转换为PKCS8格式
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key_pkcs1.key -outform PEM -nocrypt -out private_key_pkcs8.key
# pkcs8转rsa私钥
openssl pkcs8 -inform PEM -nocrypt -in private_key_pkcs8.key -traditional -out rsa_private_key_pkcs1.key
复制代码证书格式转换,如下:# 证书DER转PEM
openssl x509 -inform der -in cert.der -outform pem -out cert.pem -noout
# x509证书转pkcs7证书
openssl crl2pkcs7 -nocrl -certfile cert.crt -out cert.p7b
# 查看pkcs7证书
openssl pkcs7 -print_certs -in cert.p7b -noout
复制代码由于密钥库中包含证书与私钥,故可以从密钥库文件中提取出证书与私钥,如下:# 从pkcs12密钥库中提取证书
openssl pkcs12 -in keystore.p12 -clcerts -nokeys -out cert.crt
# 从pkcs12密钥库中提取私钥
openssl pkcs12 -in keystore.p12 -nocerts -nodes -out private_key.key
# pkcs12转jks
keytool -importkeystore -srckeystore keystore.p12 -srcstoretype pkcs12 -srcalias demo -destkeystore keystore.jks -deststoretype jks -deststorepass 123456 -destalias demo
# 从jks中提取证书
keytool -export -alias demo -keystore keystore.jks -file cert.crt
复制代码读取密钥或证书文件使用JCA来读取密钥或证书文件,也是非常方便的。PEM转DER若要将PEM格式文件转换为DER,只需要把---BEGIN XXX---与---END XXX---去掉,然后使用Base64解码即可,如下:private static byte[] pemFileToDerBytes(String pemFilePath) throws IOException {
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(pemFilePath);
String pemStr = StreamUtils.copyToString(is, StandardCharsets.UTF_8);
//去掉---BEGIN XXX---与---END XXX---
pemStr = pemStr.replaceAll("---+[^-]+---+", "")
.replaceAll("\\s+", "");
//base64解码为DER二进制内容
return Base64.getDecoder().decode(pemStr);
}
复制代码读取PKCS8私钥在JCA中,使用PKCS8EncodedKeySpec解析PKCS8私钥文件,如下:public static void testPkcs8PrivateKeyFile() {
byte[] derBytes = pemFileToDerBytes("cert/private_key_pkcs8.key");
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(derBytes);
RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey)KeyFactory.getInstance("RSA").generatePrivate(pkcs8EncodedKeySpec);
BigInteger n = rsaPrivateCrtKey.getModulus();
BigInteger e = rsaPrivateCrtKey.getPublicExponent();
BigInteger d = rsaPrivateCrtKey.getPrivateExponent();
System.out.printf(" n: %X \n e: %X \n d: %X \n", n, e, d);
BigInteger plain = BigInteger.valueOf(new Random().nextInt(1000000000));
// RSA加密
long t1 = System.nanoTime();
BigInteger secret = plain.modPow(e, n);
long t2 = System.nanoTime();
// RSA解密
BigInteger plain2 = secret.modPow(d, n);
long t3 = System.nanoTime();
System.out.printf(" plain: %d \n plain2: %d \n", plain, plain2);
System.out.printf("enc time: %d \n", (t2 - t1));
System.out.printf("dec time: %d \n", (t3 - t2));
}
复制代码读取X.509公钥在JCA中,使用X509EncodedKeySpec解析X.509公钥文件,如下:public static void testX509PublicKeyFile() {
byte[] derBytes = pemFileToDerBytes("cert/public_key_x509.key");
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(derBytes);
RSAPublicKey rsaPublicKey = (RSAPublicKey)KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec);
BigInteger e = rsaPublicKey.getPublicExponent();
BigInteger n = rsaPublicKey.getModulus();
System.out.printf(" e: %X \n n: %X \n", e, n);
}
复制代码读取X.509证书读取X.509证书文件,可使用CertificateFactory类,如下:public static void testX509CertFile() {
byte[] derBytes = pemFileToDerBytes("cert/cert.crt");
Collection<? extends Certificate> certificates = CertificateFactory.getInstance("X.509")
.generateCertificates(new ByteArrayInputStream(derBytes));
for(Certificate certificate : certificates){
X509Certificate x509Certificate = (X509Certificate)certificate;
System.out.printf("SubjectDN: %s \n", x509Certificate.getSubjectDN());
System.out.printf("IssuerDN: %s \n", x509Certificate.getIssuerDN());
System.out.printf("SigAlgName: %s \n", x509Certificate.getSigAlgName());
System.out.printf("Signature: %s \n", Hex.encodeHexString(x509Certificate.getSignature()));
System.out.printf("PublicKey: %s \n", x509Certificate.getPublicKey());
}
}
复制代码读取PKCS12密钥库文件读取PKCS12规范的密钥库文件,可使用KeyStore类,如下:public static void testPkcs12File() {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("cert/keystore.p12");
char[] password = "123456".toCharArray();
keyStore.load(is, password);
//获取证书
X509Certificate x509Certificate = (X509Certificate)keyStore.getCertificate("demo");
System.out.println("X509Certificate: ");
System.out.printf("SubjectDN: %s \n", x509Certificate.getSubjectDN());
System.out.printf("IssuerDN: %s \n", x509Certificate.getIssuerDN());
System.out.printf("SigAlgName: %s \n", x509Certificate.getSigAlgName());
System.out.printf("Signature: %s \n", Hex.encodeHexString(x509Certificate.getSignature()));
System.out.printf("PublicKey: %s \n", x509Certificate.getPublicKey());
//获取私钥
Key key = keyStore.getKey("demo", password);
System.out.printf("PrivateKey: %s \n", key);
}
复制代码如果要读取.jks文件,只需要将KeyStore.getInstance("PKCS12")中的PKCS12更换为JKS即可,其它部分保持不变,不过由于JKS是java专有格式,目前java也不推荐使用了,所以能不用的话,就尽量不要用了。常见问题证书信任问题证书的绝大多数应用场景是Https协议,但在访问https接口时,有时会由于证书信任问题导致https握手失败,主要有以下2点原因:有些公司会自建CA,使用自签证书,如早期的12306,而jdk只信任它预置的根证书,所以https握手时这种证书会认证失败。
新成立的根CA机构证书,没预置在旧的jdk里面,导致这些CA机构签发的证书不被信任。
要解决这种证书信任问题,有两种方法,如下:1. 将证书导致到jdk的预置证书库中# 将cert.crt导入jdk预置密钥库文件,密钥库文件密码默认是changeit
sudo keytool -importcert -file cert.crt -alias demo -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit
# 查看密钥库文件,检查是否导入成功keytool -list -v -alias demo -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit复制代码2. 以编码的方式信任证书以jdk自带的https sdk为例,可在代码中手动将问题证书添加到信任列表中,如下:public String testReqHttpsTrustCert() throws Exception {
// 读取jdk预置证书
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try(InputStream ksIs = new FileInputStream(System.getProperty("java.home") + "/lib/security/cacerts")) {
keyStore.load(ksIs, "changeit".toCharArray());
}
<span class="hljs-comment">// 读取证书文件</span>
<span class="hljs-type">CertificateFactory</span> <span class="hljs-variable">cf</span> <span class="hljs-operator">=</span> CertificateFactory.getInstance(<span class="hljs-string">"X.509"</span>);
<span class="hljs-keyword">try</span>(<span class="hljs-type">InputStream</span> <span class="hljs-variable">certIs</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.getClass().getResourceAsStream(<span class="hljs-string">"/cert/cert.crt"</span>)) {
<span class="hljs-type">Certificate</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> cf.generateCertificate(certIs);
keyStore.setCertificateEntry(<span class="hljs-string">"demo"</span>, c);
}
<span class="hljs-comment">// 生成信任管理器</span>
<span class="hljs-type">TrustManagerFactory</span> <span class="hljs-variable">tmFact</span> <span class="hljs-operator">=</span> TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmFact.init(keyStore);
<span class="hljs-comment">// 生成SSLSocketFactory</span>
<span class="hljs-type">SSLContext</span> <span class="hljs-variable">sslContext</span> <span class="hljs-operator">=</span> SSLContext.getInstance(<span class="hljs-string">"TLSv1.2"</span>);
sslContext.init(<span class="hljs-literal">null</span>, tmFact.getTrustManagers(), <span class="hljs-keyword">new</span> <span class="hljs-title class_">SecureRandom</span>());
<span class="hljs-type">SSLSocketFactory</span> <span class="hljs-variable">ssf</span> <span class="hljs-operator">=</span> sslContext.getSocketFactory();
<span class="hljs-comment">// 发送https请求</span>
<span class="hljs-type">URL</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">URL</span>(<span class="hljs-string">"https://www.demo.com/user/list"</span>);
<span class="hljs-type">HttpsURLConnection</span> <span class="hljs-variable">connection</span> <span class="hljs-operator">=</span> (HttpsURLConnection) url.openConnection();
connection.setHostnameVerifier((hostname, session) -&gt; hostname.endsWith(<span class="hljs-string">"demo.com"</span>));
connection.setSSLSocketFactory(ssf);
String result;
<span class="hljs-keyword">try</span>(<span class="hljs-type">InputStream</span> <span class="hljs-variable">inputStream</span> <span class="hljs-operator">=</span> connection.getInputStream()){
result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}
connection.disconnect();
<span class="hljs-keyword">return</span> result;
}复制代码
注:虽然2种方法都可以解决问题,但第1种方法使得java程序对环境形成了依赖,一旦部署环境发生变化,java程序可能就报错了,因此更推荐使用第2种方法。
总结到这里,JCA相关类的使用就介绍完了,如下表格中总结了JCA的常用类:本篇花了近一周时间整理,内容较多,对这块不太熟悉的同学,可以先关注收藏起来当示例手册,待需要时再参阅即可。

我要回帖

更多关于 知道对方id和密码能看到所有信息吗 的文章

 

随机推荐