一些常见的密码学算法的java实现


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);


© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容