title: 密码学Java
date: 2025-04-17 15:49:47
tags:
categories:
“AndroidRe”
学习目标:了解一些常见的密码学算法
Hex编码
什么是Hex编码
Hex编码是一种用16个字符表示任意二进制数据的方法。它是一种编码,而非加密算法
java代码如下:
package org.example;
import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
public static void main(String[] args) {
String name = "许之少年凌云志, 曾许人间第一流";
byte[] bytes = name.getBytes();
System.out.println(bytes);
String encode = HexBin.encode(bytes);
System.out.println(encode);
}
}
这里注意把编码方式改成UTF-8否则会报错,修改完后要重启idea
url编码与HEX编码大体相同,区别就是在每组字节前面加了个%
encode源码
static public String encode(byte[] binaryData) {
if (binaryData == null)
return null;
int lengthData = binaryData.length;
int lengthEncode = lengthData * 2;
char[] encodedData = new char[lengthEncode];
int temp;
for (int i = 0; i < lengthData; i++) {
temp = binaryData[i];
if (temp < 0)
temp += 256;
encodedData[i*2] = lookUpHexAlphabet[temp >> 4];
encodedData[i*2+1] = lookUpHexAlphabet[temp & 0xf];
}
return new String(encodedData);
}
在安卓当中使用
导入依赖
implementation ("com.squareup.okhttp3:okhttp:4.9.3")
Log.d("hyq", "测试代码");
ByteString byteString = ByteString.of("好有钱".getBytes());
System.out.println(byteString.hex());
Hex编码的特点
用0-9 a-f 16个字符表示
每个十六进制字符代表4bit,也就是2个十六进制字符代表哪一个字节
在实际引用中,比如密钥初始化,一定要分清楚传进去的密钥是哪种编码写的,采用对应方式解析,才能得到正确结果
手写代码
package org.example;
public class HexEncoder {
private static final char[] lookupHexAlphabet = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9','A', 'B', 'C', 'D', 'E', 'F'
};
public static String encode(byte[] binaryData) {
if(binaryData == null) return null;
int lengthData = binaryData.length;
int lengthEncode = lengthData * 2;
char[] encodeData = new char[lengthEncode];
int temp;
for(int i=0; i<lengthData; i++) {
temp = binaryData[i];
if(temp < 0) temp += 256;
encodeData[2*i] = lookupHexAlphabet[temp>> 4];
encodeData[2*i+1] = lookupHexAlphabet[temp & 0xF];
}
return new String(encodeData);
}
public static void main(String[] args) {
String name = "好有钱";
byte[] binaryData = name.getBytes();
System.out.println(encode(binaryData));
}
}
Base64
什么是base64
Base64是一种用64个字符表示任意二进制数据的方法。它是一种编码,而非加密算法
A-Z a-z 0-9 + / =
Base64的应用
RSA密钥、加密后的密文、图片等数据中,会有一些不可见字符。直接转换成文本传输的话,会有乱码、数据错误、数据丢失等情况出现,就可以使用Base64编码
Base64编码的实现
java中的实现
public class Main {
public static void main(String[] args) {
String s = Base64.getEncoder().encodeToString("好有钱12345678".getBytes(StandardCharsets.UTF_8));
System.out.println(s);
}
}
android中的实现
第一种,使用ByteString类
需要先导入okhttp3依赖
implementation ("com.squareup.okhttp3:okhttp:4.9.3")
ByteString byteString = ByteString.of("好有钱".getBytes());
System.out.println("byteString base64: " + byteString.base64());
第二种,在android studio中使用java库
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
String s = Base64.getEncoder().encodeToString("好有钱".getBytes());
byte[] encode = Base64.getEncoder().encode("好有钱".getBytes());
System.out.println("java.util.Base64" + new String(encode));
System.out.println("java.util.Base64" + s);
}
第三种,使用安卓自带的
String s = android.util.Base64.encodeToString("好有钱".getBytes(), 0);
System.out.println("android.util.Base64" + s);
Base64码表的妙用
为了传输数据安全,通常会对Base64数据进行URL编码,或者会把+和/替换成-和_
Base64编码细节
每个Base64字符代表原数据中的6bit
Base64编码后的字符数,是4的倍数
编码的字节数是3的倍数时,不需要填充
Base64编码的特点
Base64编码是编码,不是压缩,编码后只会增加字节数
算法可逆,解码很方便,不用于私密信息通讯
标准的Base64每行为76个字符,行末添加换行符
加密后的字符串只有65种字符,不可打印字符也可传输
在Java层可以通过hook对应方法名来快速定位关键代码
在so层可以通过输入输出的数据和码表来确定算法
MD5(消息摘要算法)
摘要长度为128bit
MD5的java实现
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update("hyq".getBytes());
md5.digest()
MD5的一些性质
加密后的字节数组可以编码成Hex、Base64
没有任何输入,也能计算hash值
碰到加salt的MD5,可以直接输入空的值,得到结果去CMD5查询一下,有可能就得到salt(但是salt不能太长)
SHA(消息摘要算法)
SHA是一系列算法,以SHA-1、SHA-256、、、命名
除了SHA-1的摘要长度是160bit,别的摘要长度都是后面的数字
SHA的Java实现
MessageDisgest sha1 = MessageDigest.getInstance("SHA-1");
sha1.update("hyq".getBytes());
sha1.digest();
SHA的一些性质
加密后的字节数组可以编码成Hex、Base64
没有任何输入,也能计算hash值
MAC系列算法(消息认证码,对称)
| 算法 | 摘要长度(bit) |
|---|---|
| HmacMD5 | 128 |
| HmacSHA1 | 160 |
| HmacSHA256 | 256 |
| HmacSHA384 | 384 |
| HmacSHA512 | 512 |
| HmacMD2 | 128 |
| HmacMD4 | 128 |
| HmacSHA224 | 224 |
MAC算法与MD和SHA的区别是多了一个密钥,密钥可以随便给
MAC的java实现
SecretKeySpec secretKeySpec = new SecretKeySpec("a12345678".getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance(secretKeySpec.getAlgorithm());
mac.init(secretKeySpec);
mac.update("hyq".getBytes());
mac.doFinla();
MAC的一些性质
加密后的字节数组可以编码成Hex、Base64
没有任何输入,也能计算hash值
DES(对称)
DES的java实现
//第一种密钥产生方式 不常用,了解下即可
DESKeySpec desKeySpec = new DESKeySpce("12345678".getBytes());
SecretKeyFactory key = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = key.generateSecret(desKeySpec);
//第二种密钥产生方式
SecretKeySpec secretKeySpec = new SecretKeySpec("12345678".getBytes(), "DES");
Cipher des = Cipher.getInstance("DES");
des.init(Ciphter.ENCRYPT_MODE, secretKeySpec);
des.doFinal("hyq".getBytes());
ECB模式和CBC模式的区别
没有指明加密模式和填充方式,表示使用默认的DES/ECB/PKCS5Padding
加密后的字节数组可以编码成Hex、Base64
DES算法明文按64位进行分组加密
要复现一个对称加密算法,需要得到以下几个东西
明文、key、iv、mode、padding
明文、key、iv需要注意解析方式,而且不一定是字符串形式
如果加密模式是ECB,则不需要iv
如果明文中有两个分组的内容相同,ECB会得到完全一样的密文,CBC不会
加密算法的结果通常与明文等长或者更长,如果变短了,那可能是gzip、protubuf
DESede(对称)
用第一个密钥加密,然后用第二个密钥解密,最后再用第三个密钥加密
DESede的java实现
// 第一种产生密钥的方式
DESedeKeySpec desedeKey = new DESedeKeySpec("1234567812345678").getBytes();
SecretKeyFactory key = SecretKeyFactory.getInstance("DESede");
SecretKeySpec secretKey = key.generateSecret(desKeySpec);
//第二种密钥产生方式
SecretKeySpec secretKeySpec = new SecretKeySpec("1234567812345678".getBytes(), "DESede");
Ciphter ciphter_desede = Cipher.getInstance("DESede");
ciphter_desede.init(Ciphter.ENCRYPT_MODE, secretKeySpec);
ciphter_desede.doFinal("hyq".getBytes());
AES(对称)
根据密钥长度的不同,可以分为AES128、AES192、AES256
AES的java实现
SecretKeySpec key = new SecretKeySpec("1234567890abcdef".getBytes(), "AES");
IvParamterSpec iv = new IvParamterSpec("1234567890abcdef".getBytes());
Ciphter aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(1, key, iv);
aes.doFinal("a12345678".getBytes())
ECB模式和CBC模式的区别
在对称加密算法里,如果使用NOPadding,加密的明文必须刚好等于分组长度倍数,否则会报错
如果使用PKCS5Padding,会对加密的明文填充1字节-1个分组的长度
RSA_Base64(非对称)
私钥的格式
pkcs1格式通常开头是 ——BEGIN RSA PRIVATE KEY——
pkcs8格式通常开头是 ——BEGIN PRIVATE KEY——
Java中的私钥必须是pkcs8格式
RSA密钥的解析
byte[] keyBytes = Base64Decoder.decodeBuffer(key);
X509EncodeKeySpec keySpec = new X509EncodedKeySPec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
byte[] keyBytes = Base64Decoder.decodeBuffer(key);
PKCS8EncodeKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generetePrivate(keySpec);
RSA加解密
Cipher cipher = Cipher.getInstance("RSA/None/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bt_encrypted = cipher.doFinal(bt_plaintext);
Cipher cipher = Cipher.getInstance("RSA/None/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bt_orinial = cipher.doFinal(bt_encrypted);
RSA模式和填充细节
None模式和ECB模式是一致的
NoPadding
明文最多字节数位密钥字节数
密文与密钥等长
填充字节0,加密后的密文不变
pkcs1padding
明文最大字节数为密钥字节数-11
密文与密钥等长
每一次的填充不一样,使得加密后的密文会变
把pkcs1padding加密后的密文,用NoPadding去解密,会得到填充的字节
没有指明加密模式和填充方式,表示使用默认的RSA/ECB/NOPadding
加密后的字节数组可以编码成Hex、Base64
RSA_Hex
RSA密钥的解析
BigInteger N = new BigInteger(stringN, 16);
BigInteger E = new BigInteger(stringE, 16);
RSAPublicKeySpec spec = new RSAPublicKeySpec("RSA");
PublicKey publicKey = keyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(spec);
多种加密算法的常见结合套路
随机生成AES密钥A
A密钥用于AES加密数据,得到数据密文B
使用RSA对A密钥加密,得到密钥密文C
提交密钥密文C和数据密文B给服务器
MainActivity.java
package com.example.demo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.example.demo.databinding.ActivityMainBinding;
import java.util.Base64;
import java.util.Random;
import okio.ByteString;
public class MainActivity extends AppCompatActivity {
// Used to load the 'demo' library on application startup.
static {
System.loadLibrary("demo");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
try{
//客户端加密部分 cipherText和cipherKey 需要同时提交给服务器
String AESKey = generateAESKey();
String cipherText = AES.encryptAES("hyq", AESKey);
String cipherKey = RSA_Base64.encryptByPublicKey(AESKey);
//服务器解密部分
String plainKey = RSA_Base64.decryptByPrivateKey(cipherKey);
String plainText = AES.decryptAES(ByteString.decodeBase64(cipherText).toByteArray(), plainKey);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* A native method that is implemented by the 'demo' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public static String generateAESKey(){
StringBuffer stringBuffer = new StringBuffer();
Random random = new Random();
for(int i = 0; i < 16; i++){
int random_i = random.nextInt(100);
String temp = "0" + Integer.toHexString(random_i);
temp = temp.substring(temp.length() - 2);
stringBuffer.append(temp);
}
String resultKey = stringBuffer.toString();
return resultKey;
}
}
AES.java
package com.example.demo;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import okio.ByteString;
public class AES {
public static String encryptAES(String plainText, String AESKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
SecretKeySpec aesKey = new SecretKeySpec(ByteString.decodeHex(AESKey).toByteArray(), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec("0123456789abcdef".getBytes());
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.ENCRYPT_MODE, aesKey, ivParameterSpec);
byte[] bytes = aes.doFinal(plainText.getBytes());
ByteString of = ByteString.of(bytes);
return of.base64();
}
public static String decryptAES(String plainText, String AESKey) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException {
SecretKeySpec aesKey = new SecretKeySpec(ByteString.decodeHex(AESKey).toByteArray(), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec("0123456789abcdef".getBytes());
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
byte[] bytes = aes.doFinal(plainText.getBytes());
return new String(bytes);
}
}
RSA_Base64.java
package com.example.demo;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import okio.ByteString;
public class RSA_Base64 {
public static String publicKeyBase64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDxRQHxL/8xZ1EaNmQBGZnpMiCY" +
"7gRzog6nDjfBJacytEiVJnJRuq1V/D+JKaXDwetsCnSUaz65LCFHU09OSEYee5oC" +
"iI0ql21EA306c91oT/fQpPngQGZHLUtDOUdJVlAKnicCvmR24NqyNKFuY8L0cnB1" +
"zcax73Rf+Ctf/lxAOwIDAQAB";
public static String privateKeyBase64 = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAPFFAfEv/zFnURo2
" +
"ZAEZmekyIJjuBHOiDqcON8ElpzK0SJUmclG6rVX8P4kppcPB62wKdJRrPrksIUdT
" +
"T05IRh57mgKIjSqXbUQDfTpz3WhP99Ck+eBAZkctS0M5R0lWUAqeJwK+ZHbg2rI0
" +
"oW5jwvRycHXNxrHvdF/4K1/+XEA7AgMBAAECgYEAsGkDrYWps0bW7zKb1o4Qkojb
" +
"etZ2HNJ+ojlsHObaJOHbPGs7JXU4bmmdTz5LfSIacAoJCciMuTqCLrPEhfmkghPq
" +
"U2MjyjfqYdXALoP7l/vt6QmjY/g1IAsaZN9nFhyjJ2WzgOx1f7gZj4NBSvTdSj7H
" +
"m5E24zkm+p7Qw1z6/mkCQQD7WSXAXcv2v3Vo6qi1FUlkzQgCQLFYqXNSOSPpno3y
" +
"oohUFIkMj0bYGbVE1LzV30Rb6Z8e8yQAByw6l8RuGb2PAkEA9bwb2euyOe6CcqpE
" +
"PNFc+7UlOJAy5epVFKHbu0aNivVpU0hsphqjIGXJGHYTspyEOLqtzILqKPZr6pru
" +
"WvJUlQJBAJoImQUZtlyCGs7wN/G5mN/ocscGpGikd+Lk16hdHbqbdpaoexCyYYUf
" +
"xCHpicw75mW5d2V9Ngu6WZWS2rNqnOsCQCoMK//X8sEy7KNOOyrk8DIpxtqs4eix
" +
"dil3oK+k3OdgIsubYuvxNuR+RjCnU6uGWKGUX9TUudiUgda89/gb6xkCQFm8gD6n
" +
"AyN+PPPKRq2M84+cAbnvjdIAY3OFHfkaoWCtEj5DR0UDuVv7jN7+re2D7id/GkAe
" +
"FAmhvYQwwLnifrw=";
public static PublicKey generatePublicKey() throws Exception{
byte[] publicKeyBase64Bytes = ByteString.decodeBase64(publicKeyBase64).toByteArray();
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBase64Bytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(x509EncodedKeySpec);
}
public static PrivateKey generatePrivateKey() throws Exception{
byte[] privateKeyBase64Bytes = ByteString.decodeBase64(privateKeyBase64).toByteArray();
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBase64Bytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
}
public static String encryptByPublicKey(String plainText) throws Exception{
PublicKey publicKey = generatePublicKey();
Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
instance.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = instance.doFinal(plainText.getBytes());
ByteString of = ByteString.of(bytes);
return of.base64();
}
public static String decryptByPrivateKey(String cipherText) throws Exception{
PrivateKey privateKey = generatePrivateKey();
Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
instance.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = instance.doFinal(cipherText.getBytes());
return new String(bytes);
}
}
数字签名算法
签名
PrivateKey priK = getPrivateKey(str_priK);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(priK);
sig.update(data);
sig.sign();
验证
PublicKey pubK = getPublicKey(str_pubK);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubK);
sig.update(data);
sig.verify(sign);


















暂无评论内容