Android 国4加密(SM4)那些事儿

今天公司突然要在网络返回数据上进行国4加密。在网上百度了一下,国4加密五花八门,而且问题还特别多。通过不懈寻找终于将国4加密安排上了,在这里把国4加密的步骤和坑一一记录一下,希望能对安卓开发的朋友有所帮助。

国4加密项目集成步骤:

1、在app的build.gradle中引入依赖包

implementation 'org.bouncycastle:bcprov-jdk15on:1.68'

2、编写工具类

注意:本工具类加密方法会对加密后的字符串进行Base64编码,解密也是先Base64解码然后再进行解密的

import com.blankj.utilcode.util.StringUtils;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.encoders.Base64;

import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.Security;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

/**
 * SM4加密算法 对称加密
 */
public class Sm4Util {

    /**
     * 算法名称
     */
    private static final String ALGORITHM_NAME = "SM4";

    /**
     * 密钥长度
     */
    private static final int KEY_LENGTH = 32;

    /**
     * 补位字符
     */
    private static final String RESERVE_CHAR = "a";

    /**
     * 加密算法/分组加密模式/分组填充方式
     * PKCS5Padding-以8个字节为一组进行分组加密
     * 定义分组加密模式使用:PKCS7Padding
     */
    private static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS7Padding";

    private static final String BC = "BC";

    static {
        if (Security.getProvider(BC) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 加密
     *
     * @param plainText 明文
     * @param key       密钥
     * @return
     * @throws Exception
     */
    public static String encode(String plainText, String key) throws Exception {
        byte[] srcData = plainText.getBytes();
        byte[] keyData = ByteUtils.fromHexString(key);
        return Base64.toBase64String(encrypt(srcData, keyData));
    }

    /**
     * 解密
     *
     * @param cipherText 密文
     * @param key        密钥
     * @return
     * @throws Exception
     */
    public static String decode(String cipherText, String key) throws Exception {
        byte[] cipherData = Base64.decode(cipherText);
        byte[] keyData = ByteUtils.fromHexString(key);
        byte[] ret = decrypt(cipherData, keyData);
        return new String(ret).trim();
    }

    /**
     * 生成密钥
     * 128 - 32位16进制,256 - 64位16进制
     *
     * @return
     */
    public static String generateKey(String str) {
        // 转为16进制字符串
        String key = ByteUtils.toHexString(str.getBytes(StandardCharsets.UTF_8));
        if (key.length() < KEY_LENGTH) {
            StringBuilder stringBuilder = new StringBuilder(key);
            // 密钥长度不够时补全
            while (stringBuilder.length() < KEY_LENGTH) {
                stringBuilder.append(RESERVE_CHAR);
            }
            return stringBuilder.toString();
        } else {
            // 密钥长度超出时只截取32位
            return key.substring(0, KEY_LENGTH);
        }
    }

    /**
     * 使用字符串作为密钥加密
     *
     * @param plainText 明文
     * @param str       字符串密钥
     * @return
     */
    public static String encodeByStr(String plainText, String str) throws Exception {
        try {
            if (StringUtils.isEmpty(plainText) || StringUtils.isEmpty(str)) {
                throw new Exception("明文或字符串密钥为空");
            }
            // 使用字符串作为密钥进行SM4加密
            return encode(plainText, generateKey(str));
        } catch (Exception e) {
            throw new Exception("加密失败," + e.getMessage());
        }
    }

    /**
     * 使用字符串作为密钥加密解密
     *
     * @param cipherText 密文
     * @param str        字符串密钥
     * @return
     */
    public static String decodeByStr(String cipherText, String str) throws Exception {
        try {
            if (StringUtils.isEmpty(cipherText) || StringUtils.isEmpty(str)) {
                throw new Exception("密文或字符串密钥为空");
            }
            // 使用字符串作为密钥进行SM4解密
            return decode(cipherText, generateKey(str));
        } catch (Exception e) {
            throw new Exception("解密失败," + e.getMessage());
        }
    }



    /**
     * 加密为16进制字符串
     *
     * @param plainText 明文
     * @param key       密钥
     * @return
     * @throws Exception
     */
    public static String encodeToHex(String plainText, String key) throws Exception {
        byte[] srcData = plainText.getBytes();
        byte[] keyData = ByteUtils.fromHexString(key);
        return ByteUtils.toHexString(encrypt(srcData, keyData));
    }


    /**
     * 加密
     *
     * @param plainText 明文
     * @param key       密钥
     * @return
     * @throws Exception
     */
    public static byte[] encrypt(byte[] plainText, byte[] key) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(plainText);
    }



    /**
     * 解密16进制字符串
     *
     * @param cipherText 密文
     * @param key        密钥
     * @return
     * @throws Exception
     */
    public static String decodeFromHex(String cipherText, String key) throws Exception {
        byte[] cipherData = ByteUtils.fromHexString(cipherText);
        byte[] keyData = ByteUtils.fromHexString(key);
        byte[] ret = decrypt(cipherData, keyData);
        return new String(ret).trim();
    }

    /**
     * 解密
     *
     * @param cipherText 密文
     * @param key        密钥
     * @return
     * @throws Exception
     */
    public static byte[] decrypt(byte[] cipherText, byte[] key) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }

    /**
     * 生成ECB暗号
     *
     * @param algName 算法名称
     * @param mode    模式
     * @param key     密钥
     * @return
     * @throws Exception
     */
    private static Cipher generateEcbCipher(String algName, int mode, byte[] key) throws Exception {
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(algName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        cipher.init(mode, sm4Key);
        return cipher;
    }
}

3、加入混淆

#国四加密混淆
-keep class org.bouncycastle.** { *; }

注意事项

在实践过程中很容易爆NoSuchAlgorithmException: Provider BC does not provide SM4 hutool这个错误,按上面的集成过程应该不会遇到这个错误。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Android SM2、SM3、SM4 算法支持 Service Provider 及证书制作软件包 密算法 JCAJCE Service Provider,适应版本 Android 4.2.2~7.0 支持 SM2 的 KeyFactory、KeyPairGenerator、Cipher、Signature、X.509 CertificateFactory 接口 支持 SM3 的 MessageDigest 接口、SM3withSM2 混合算法 支持 SM4 的 Cipher、KeyFactory、KeyGenerator、SecretKey 接口、相关算法 CMAC-SM4、Poly1305-SM4 增加 java.security.PublicKey 的子类 SM2PublicKey 增加 java.security.PrivateKey 的子类 SM2PrivateKey 全功能支持 SM3withSM2 算法的 X.509 证书结构体解释与密码运算 支持 BKS、PKCS#12 KeyStore 生成、解释、验算 X.509v1/v3 证书,签名算法支持 SM3withSM2、主流 RSA、DSA、ECDSA.... 生成、解释、验算 PKCS#10 证书申请,签名算法支持 SM3withSM2、主流 RSA、DSA、ECDSA.... *** 无须打包 BouncyCastle 支持库,体积小、节约内存 *** 请参阅 testSM.java、testCERT.java 文件列表: 1、AndroidSM.jar -- SM2、SM3、SM4 算法/证书支持的 JCA/JCE Service Provider 类库 2、AndroidCRT.jar -- X.509 数字证书/PKCS#10 证书申请相关类库 3、bc422.jar -- BouncyCastle 加密库,Android 4.2.2 内置版本(由真机导出dex文件转换而得,仅用于编译时选用,勿打包到apk文件中) 4、testSM.java -- SM2、SM3、SM4 算法相关类引用范例 5、testCERT.java -- X.509 数字证书/PKCS#10 证书申请相关类引用范例 6、readme.txt -- 本文 因条件及精力限制,各类、方法的实现未经严格彻底的测试,不宜用于商业用途软件的开发。 如欲将本开发包发布、上传、拷贝、共享等,务必保持其内容完整性(包括本文) 如有需要帮助或者索取源码,请联系 suntongo@qq.com, suntongo@hotmail.com
SM4是一种对称加密算法,可以用于Android图片文件的加密和解密。以下是一个简单的示例代码,演示如何使用SM4加密和解密图片文件: ```java import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Base64; import com.tongtech.cryptolib.SM4Utils; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; public class ImageUtils { /** * 加密图片文件 * * @param context 上下文对象 * @param imagePath 图片文件路径 * @param key 密钥 * @param iv 初始向量 * @param encryptDir 加密后保存的目录 * @return 加密后的图片文件路径 * @throws Exception */ public static String encryptImage(Context context, String imagePath, String key, String iv, String encryptDir) throws Exception { // 读取图片文件 FileInputStream inputStream = new FileInputStream(imagePath); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, len); } byte[] data = outputStream.toByteArray(); inputStream.close(); outputStream.close(); // 使用SM4算法加密数据 SM4Utils sm4Utils = new SM4Utils(); sm4Utils.setSecretKey(key); sm4Utils.setIv(iv); byte[] encryptedData = sm4Utils.encryptData_ECB(data); // 将加密后的数据转换成Base64编码字符串 String base64Data = Base64.encodeToString(encryptedData, Base64.DEFAULT); // 将加密后的数据写入文件 String encryptedPath = encryptDir + "/encrypted_image.jpg"; FileOutputStream outputStream1 = new FileOutputStream(encryptedPath); outputStream1.write(base64Data.getBytes()); outputStream1.close(); return encryptedPath; } /** * 解密图片文件 * * @param context 上下文对象 * @param encryptedPath 加密后的图片文件路径 * @param key 密钥 * @param iv 初始向量 * @return 解密后的Bitmap对象 * @throws Exception */ public static Bitmap decryptImage(Context context, String encryptedPath, String key, String iv) throws Exception { // 读取加密后的数据 FileInputStream inputStream = new FileInputStream(encryptedPath); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, len); } String base64Data = outputStream.toString(); byte[] encryptedData = Base64.decode(base64Data, Base64.DEFAULT); inputStream.close(); outputStream.close(); // 使用SM4算法解密数据 SM4Utils sm4Utils = new SM4Utils(); sm4Utils.setSecretKey(key); sm4Utils.setIv(iv); byte[] data = sm4Utils.decryptData_ECB(encryptedData); // 将解密后的数据转换成Bitmap对象 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); return bitmap; } } ``` 需要注意的是,加密和解密时使用的密钥和初始向量应该保持一致,否则解密会失败。另外,该示例中使用的是SM4算法的ECB模式,也可以使用其他模式,如CBC等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值