Android 国密SM4-基于BC库(BouncyCastle)实现

895 阅读7分钟

SM4概念介绍

SM4是一种对称加密算法,也被称为国密算法或商密算法。它是中国国家密码管理局(State Cryptography Administration,SCA)发布的一套国家密码标准中的一部分,用于数据加密和保护敏感信息。SM4算法的设计旨在提供高度的安全性和性能,适用于各种应用场景,包括电子商务、金融交易、通信系统等。

SM4算法的一些重要特点和特性:

  1. 分组大小和密钥长度: SM4算法采用128位的分组大小(16字节)和128位的密钥长度。这种分组大小和密钥长度使得SM4适用于大多数安全需求。
  2. 分组密码: SM4是一种分组密码,意味着它将输入数据分成固定大小的块(分组),然后对每个分组应用加密算法。这种模式允许SM4对长数据进行安全且高效的加密。
  3. Feistel结构: SM4采用Feistel结构,这是一种常见的对称加密算法结构。在Feistel结构中,输入分组会被分成两部分,然后通过多轮的加密运算和密钥混合来生成输出。
  4. S盒和轮密钥: SM4使用了S盒(Substitution Box)和轮密钥(Round Key)。S盒执行字节替换,轮密钥用于每轮加密时的密钥混合。
  5. 高度的混淆和扩散: SM4注重混淆(confusion)和扩散(diffusion)的设计原则,这有助于提高算法的安全性和抵抗各种攻击。
  6. 多轮结构: SM4算法进行多轮的加密运算,每轮包括S盒替换、线性变换、轮密钥混合等步骤。多轮设计增强了算法的复杂性和安全性。
  7. 密钥扩展: SM4使用密钥扩展算法来生成每轮加密所需的轮密钥。密钥扩展确保了每轮的密钥都是独立的,增加了攻击者破解密钥的难度。

SM4加密模式

不同的加密模式适用于不同的场景和需求,以下是一些常见的加密模式及其适用情况:

  1. ECB(Electronic Codebook)模式:
    • 每个数据块独立加密,不考虑前后数据的关系。
    • 适用于小数据块的加密,不适合加密大量数据。
    • 不适用于保护数据完整性,因为相同的明文块会生成相同的密文块。
  1. CBC(Cipher Block Chaining)模式:
    • 需要提供一个初始向量(IV)来增加数据的随机性。
    • 适用于需要保护数据完整性且数据块较大的情况。
    • 可以防止明文块的重复,但是对初始向量的管理要求较高。
  1. CFB(Cipher Feedback)模式:
    • 允许部分解密,适用于需要在加密和解密之间进行频繁切换的场景。
    • 可以加密小块数据。
    • 对于大数据流的加密,性能可能较差。
  1. OFB(Output Feedback)模式:
    • 类似于 CFB,但更适用于流加密,因为密钥流可以预先生成。
    • 可以加密小块数据。
    • 与 CFB 类似,性能可能较差。
  1. CTR(Counter)模式:
    • 允许并行加密和解密,适用于大数据流的加密。
    • 密钥流的生成与数据无关,可以预先生成。
    • 不需要初始向量,但需要一个唯一的计数器。

加密模式的选择取决于你的应用需求和安全性要求。例如,对于数据块较大的情况,CBC 模式可能更适合,而对于大数据流的加密,CTR 模式可能更有效。在实际应用中,还需要考虑密钥管理、数据填充、初始向量的安全性、异常处理等因素。选择适合的加密模式是确保数据安全性的重要一步。

SM4加密算法使用流程

  1. 选择加密模式: 首先,你需要确定要使用的加密模式,如 ECB、CBC、CFB、OFB、CTR 等。不同的加密模式适用于不同的场景和需求。
  2. 生成密钥: 生成一个合适长度的密钥,SM4 使用的密钥长度为 128 位(16 字节)。
  3. 选择初始向量(IV): 对于需要使用 CBC、CFB、OFB、CTR 等模式的加密,你需要选择一个合适的初始向量(IV)。
  4. 数据填充: 对于分组密码算法,要求数据长度必须是分组大小的整数倍。如果原始数据不是分组大小的整数倍,需要进行数据填充,常用的填充方式有 PKCS#7 和 Zero Padding。
  5. 加密: 使用选择的加密模式,将填充后的数据和生成的密钥传入 SM4 加密算法中进行加密操作。
  6. 解密: 如果需要解密,将加密后的数据和相应的密钥传入 SM4 解密算法中进行解密操作。
  7. 处理解密结果: 解密得到的数据可能包含填充字节,需要根据填充方式进行相应的处理,以获取原始数据。

在实际应用中,根据数据保护的需求和安全性要求,选择合适的加密模式,并遵循密码学的最佳实践来确保数据的安全性和完整性。同时,还需要考虑异常处理、密钥管理以及与其他安全机制的配合。

jar包配置参考文章

SM4加密工具类

使用bc库进行SM4加密,填充模式有以下几种

  • NoPadding: 该填充方式不对数据进行填充,要求明文数据的长度必须是块大小的倍数(16字节),否则会抛出异常。
  • PKCS5Padding: 基于PKCS#5标准的填充方式,适用于块密码加密,会根据明文长度在明文末尾添加相应数量的填充字节,使得明文长度成为块大小的倍数。
  • ISO10126Padding: 该填充方式将明文末尾填充随机字节,其中最后一个字节指示填充的字节数。
  • X923Padding: 与ISO10126Padding类似,但最后一个字节是填充的字节数,其他字节填充0。

ECB和CBC使用NoPadding填充模式时,需要加密字段为16位整数倍,才能正常进行加密,否则会提示:data not block size aligned 异常

其他模式没有这个限制,可以正常进行加密解密。

完整SM4工具类

package com.example.testvpndemo.util;

import android.util.Base64;
import android.util.Log;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;

import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;

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

/**
 * @Description SM4 工具类
 * @Time 2023/8/15 9:11
 * @Author gz
 **/
public class SM4Util {
    private static String TAG = SM4Util.class.getSimpleName();
    public static final int DEFAULT_KEY_SIZE = 128;
    public static final String ALGORITHM_NAME = "SM4";


    static {
        double version = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME).getVersion();
        Log.i(TAG, "generateKey1: " + version);
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Log.i("sys", "运行环境没有BouncyCastleProvider");
            Security.addProvider(new BouncyCastleProvider());
        }
        version = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME).getVersion();
        Log.i(TAG, "generateKey2: " + version);
    }


    /**
     * 生成密钥
     *
     * @return
     * @explain
     */
    public static String generateKey() {
        // 创建一个安全的随机数生成器
        SecureRandom secureRandom = new SecureRandom();
        // 生成一个 128 位(16 字节)的随机密钥
        byte[] keyBytes = new byte[16];
        secureRandom.nextBytes(keyBytes);
        return Hex.toHexString(keyBytes);
    }


    /**
     * 生成初始化向量iv
     */
    public static AlgorithmParameters generateIV() throws Exception {
        SecureRandom secureRandom = new SecureRandom();
        byte[] iv = new byte[16];
        //随机生成bytes数组
        secureRandom.nextBytes(iv);
        AlgorithmParameters params = AlgorithmParameters.getInstance(ALGORITHM_NAME);
        IvParameterSpec spec = new IvParameterSpec(iv);
        params.init(spec);
        Log.i(TAG, "generateKey: iv:" + iv);
        return params;
    }

    /**
     * @param algorithmName 算法名称
     * @param key           密钥
     * @param iv            初始向量
     * @param data          加密/解密数据 Hex.decode()
     * @return 加密后数据
     * @throws Exception
     * @description 加密
     */
    public static byte[] encrypt(String algorithmName, byte[] key, byte[] iv, byte[] data) throws Exception {
        return sm4Core(algorithmName, Cipher.ENCRYPT_MODE, key, iv, data);
    }

    /**
     * @param algorithmName 算法名称
     * @param key           密钥
     * @param iv            初始向量
     * @param data          加密数据
     * @return 解密结果
     * @throws Exception
     * @description 解密
     */
    public static byte[] decrypt(String algorithmName, byte[] key, byte[] iv, byte[] data) throws Exception {
        return sm4Core(algorithmName, Cipher.DECRYPT_MODE, key, iv, data);
    }

    /**
     * 加密/解密
     *
     * @param algorithmName 算法名称
     * @param type          加密/解密
     * @param key           密钥
     * @param iv            初始向量
     * @param data          加密/解密数据
     * @return 结果
     * @throws Exception
     */
    private static byte[] sm4Core(String algorithmName, int type, byte[] key, byte[] iv, byte[] data)
    throws Exception {
            //指定加密算法
            Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
            Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);// 初始化加密器,设置加密模式和初始向量
            //ecb不需要设置初始向量
            if (algorithmName.contains("/ECB/")) {
                cipher.init(type, sm4Key);
            } else {
                // 创建初始向量参数
                IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
                //// 初始化加密器,设置加密模式和初始向量
                cipher.init(type, sm4Key, ivParameterSpec);
            }
            return cipher.doFinal(data);
        }
    }

密钥生成

try {
    createKey = SM4Util.generateKey();
    algorithmParameters = SM4Util.generateIV();
    IvParameterSpec spec = SM4Util.generateIV().getParameterSpec(IvParameterSpec.class);
    ivBytes = spec.getIV();
    String generateIV = Hex.toHexString(ivBytes);
    viewModel.sm4CreateKey.set("密钥:\n" + createKey + "\n向量IV:\n" + generateIV);
} catch (Exception e) {
    e.printStackTrace();
}

加密解密调用

ECB加密调用

try {
    ECBEncodeByte = SM4Util.encrypt("SM4/ECB/" + chooseMode, Hex.decode(createKey), ivBytes, paramStr);
    cipher = Hex.toHexString(ECBEncodeByte);
    System.out.println(cipher);
} catch (Exception e) {
    cipher = "加密失败:" + e.getMessage();
    e.printStackTrace();
}

ECB解密调用

try {
    byte[] bytes;
    bytes = SM4Util.decrypt("SM4/ECB/" + chooseMode, Hex.decode(createKey), ivBytes, tempBytes);
    result = new String(bytes);
} catch (Exception e) {
    result = "解密失败" + e.getMessage();
    e.printStackTrace();
}