laitimes

Domestic encryption application: use SM3 to salt the password, and use SM2 for login authentication

1. Be brief

This article combines the actual code of the person, uses SM3 to salt the password, and uses SM2 for login authentication. There are two main points to understand:

1. When adding a user, the plaintext password is encrypted by SM3 and then salted (randomly generated) to form a ciphertext and stored in the database, and we also store the salt in a field of the user table (for the check comparison of the password when logging in). Users do not need to update the salt storage when they modify it.

2. When logging in and authenticating, the account and password are encrypted with sm2 through a fixed public key at the front end and then transmitted to the back end, and the back end decrypts the account and password through a fixed private key, and the decrypted password is encrypted by sm3 and salted and compared with the ciphertext in the database for authentication.

3. The following code is all key code, not all code, but enough for your reference to implement the function.

2. Development environment and tools

java,idea,mybatis-plus,spring boot,vue

3. Background password encryption part

The background receives the password plaintext, encrypts it, and saves it.

3.1 Encryption Code

User table (SysUser), password field (password), salt field (salt)

byte[] mySalt = Sm3crypto.getSalt();
        //将盐用base64转化为字符串存到数据库中
        sysUser.setSalt(Base64.getEncoder().encodeToString(mySalt));
        //用密码sm3加密后再加盐,形成新的密码
        sysUser.setPassword(Hex.toHexString(Sm3crypto.pwdSaltedHashValue(mySalt, sysUser.getPassword())));
        this.save(sysUser);           

3.2 SM3 encryption class (Sm3crypto)

import cn.stylefeng.guns.core.util.Sm3Utils;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;
 
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Base64;
 
//SM3加密
public class Sm3crypto {
 
 
    public static byte[] getSalt(){
        /*
         * 随机生成128位的随机数
         */
        SecureRandom random = new SecureRandom();
        byte bytes1[] = new byte[16];
        random.nextBytes(bytes1);
        return bytes1;
    }
 
    public static byte[] pwdSaltedHashValue(byte[] bytes1, String passwdString) {
 
        //sm3加密密码
        try {
            passwdString = Sm3Utils.encodeSM3(passwdString);
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        /*
         * 加盐:即随机数和口令组合
         */
        byte passwdbyte[]= arraycat(bytes1,passwdString.getBytes());
        //SM3计算
        SM3Digest mdDigest=new SM3Digest();
        mdDigest.update(passwdbyte,0,passwdbyte.length);
        byte[] result=new byte[mdDigest.getDigestSize()];
        mdDigest.doFinal(result, 0);
        return result;
    }
    /*
     * 拼接buf1和buf2数组
     */
    public static byte[] arraycat(byte[] buf1,byte[] buf2)
    {
 
        byte[] bufret=null;
        int len1=0;
        int len2=0;
        if(buf1!=null)
            len1=buf1.length;
        if(buf2!=null)
            len2=buf2.length;
        if(len1+len2>0)
            bufret=new byte[len1+len2];
        if(len1>0)
            System.arraycopy(buf1,0,bufret,0,len1);
        if(len2>0)
            System.arraycopy(buf2,0,bufret,len1,len2);
        return bufret;
    }
 
 
 
}           

3.3 SM3 Tools (Sm3Utils)

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.Arrays;
 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
 
/**
 * 国密SM3,消息摘要(MD5)
 *
 * @author Luke
 */
@Slf4j
public class Sm3Utils {
 
    private static char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    public static final byte[] IV = {0x73, (byte) 0x80, 0x16, 0x6f, 0x49, 0x14, (byte) 0xb2, (byte) 0xb9, 0x17, 0x24, 0x42,
            (byte) 0xd7, (byte) 0xda, (byte) 0x8a, 0x06, 0x00, (byte) 0xa9, 0x6f, 0x30, (byte) 0xbc, (byte) 0x16, 0x31,
            0x38, (byte) 0xaa, (byte) 0xe3, (byte) 0x8d, (byte) 0xee, 0x4d, (byte) 0xb0, (byte) 0xfb, 0x0e, 0x4e};
    private static final Integer TJ_15 = Integer.valueOf("79cc4519", 16);
    private static final Integer TJ_63 = Integer.valueOf("7a879d8a", 16);
    private static final byte[] FirstPadding = {(byte) 0x80};
    private static final byte[] ZeroPadding = {(byte) 0x00};
 
    private static int T(int j) {
        if (j >= 0 && j <= 15) {
            return TJ_15.intValue();
        } else if (j >= 16 && j <= 63) {
            return TJ_63.intValue();
        } else {
            throw new RuntimeException("data invalid");
        }
    }
 
    private static Integer FF(Integer x, Integer y, Integer z, int j) {
        if (j >= 0 && j <= 15) {
            return Integer.valueOf(x.intValue() ^ y.intValue() ^ z.intValue());
        } else if (j >= 16 && j <= 63) {
            return Integer.valueOf(
                    (x.intValue() & y.intValue()) | (x.intValue() & z.intValue()) | (y.intValue() & z.intValue()));
        } else {
            throw new RuntimeException("data invalid");
        }
    }
 
    private static Integer GG(Integer x, Integer y, Integer z, int j) {
        if (j >= 0 && j <= 15) {
            return Integer.valueOf(x.intValue() ^ y.intValue() ^ z.intValue());
        } else if (j >= 16 && j <= 63) {
            return Integer.valueOf((x.intValue() & y.intValue()) | (~x.intValue() & z.intValue()));
        } else {
            throw new RuntimeException("data invalid");
        }
    }
 
    private static Integer P0(Integer x) {
        return Integer
                .valueOf(x.intValue() ^ Integer.rotateLeft(x.intValue(), 9) ^ Integer.rotateLeft(x.intValue(), 17));
    }
 
    private static Integer P1(Integer x) {
        return Integer.valueOf(x.intValue() ^ Integer.rotateLeft(x.intValue(), 15) ^ Integer.rotateLeft(x.intValue(), 23));
    }
 
    private static byte[] padding(byte[] source) throws IOException {
        if (source.length >= 0x2000000000000000L) {
            throw new RuntimeException("src data invalid.");
        }
        long l = source.length * 8;
        long k = 448 - (l + 1) % 512;
        if (k < 0) {
            k = k + 512;
        }
        if (log.isDebugEnabled()) {
            log.debug("k = " + k);
        }
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
            baos.write(source);
            baos.write(FirstPadding);
            long i = k - 7;
            while (i > 0) {
                baos.write(ZeroPadding);
                i -= 8;
            }
            baos.write(long2bytes(l));
            if (log.isDebugEnabled()) {
                log.debug("paded size = " + baos.size());
            }
            return baos.toByteArray();
        }
    }
 
    private static byte[] long2bytes(long l) {
        byte[] bytes = new byte[8];
        for (int i = 0; i < 8; i++) {
            bytes[i] = (byte) (l >>> ((7 - i) * 8));
        }
        return bytes;
    }
 
    public static String encodeSM3(String source) throws IOException {
        byte[] b = encodeSM3(source.getBytes());
        return byteToHexString(b);
    }
 
    public static byte[] encodeSM3(byte[] source) throws IOException {
        byte[] m1 = padding(source);
        int n = m1.length / (512 / 8);
        if (log.isDebugEnabled()) {
            log.debug("n = " + n);
        }
        byte[] b;
        byte[] vi = IV.clone();
        byte[] vi1 = null;
        for (int i = 0; i < n; i++) {
            b = Arrays.copyOfRange(m1, i * 64, (i + 1) * 64);
            vi1 = CF(vi, b);
            vi = vi1;
        }
        return vi1;
    }
 
    private static byte[] CF(byte[] vi, byte[] bi) throws IOException {
        int a, b, c, d, e, f, g, h;
        a = toInteger(vi, 0);
        b = toInteger(vi, 1);
        c = toInteger(vi, 2);
        d = toInteger(vi, 3);
        e = toInteger(vi, 4);
        f = toInteger(vi, 5);
        g = toInteger(vi, 6);
        h = toInteger(vi, 7);
 
        int[] w = new int[68];
        int[] w1 = new int[64];
        for (int i = 0; i < 16; i++) {
            w[i] = toInteger(bi, i);
        }
        for (int j = 16; j < 68; j++) {
            w[j] = P1(w[j - 16] ^ w[j - 9] ^ Integer.rotateLeft(w[j - 3], 15)) ^ Integer.rotateLeft(w[j - 13], 7)
                    ^ w[j - 6];
        }
        for (int j = 0; j < 64; j++) {
            w1[j] = w[j] ^ w[j + 4];
        }
        int ss1, ss2, tt1, tt2;
        for (int j = 0; j < 64; j++) {
            ss1 = Integer.rotateLeft(Integer.rotateLeft(a, 12) + e + Integer.rotateLeft(T(j), j), 7);
            ss2 = ss1 ^ Integer.rotateLeft(a, 12);
            tt1 = FF(a, b, c, j) + d + ss2 + w1[j];
            tt2 = GG(e, f, g, j) + h + ss1 + w[j];
            d = c;
            c = Integer.rotateLeft(b, 9);
            b = a;
            a = tt1;
            h = g;
            g = Integer.rotateLeft(f, 19);
            f = e;
            e = P0(tt2);
        }
        byte[] v = toByteArray(a, b, c, d, e, f, g, h);
        for (int i = 0; i < v.length; i++) {
            v[i] = (byte) (v[i] ^ vi[i]);
        }
        return v;
    }
 
    private static int toInteger(byte[] source, int index) {
        StringBuilder valueStr = new StringBuilder("");
        for (int i = 0; i < 4; i++) {
            valueStr.append(chars[(byte) ((source[index * 4 + i] & 0xF0) >> 4)]);
            valueStr.append(chars[(byte) (source[index * 4 + i] & 0x0F)]);
        }
        return Long.valueOf(valueStr.toString(), 16).intValue();
 
    }
 
    private static byte[] toByteArray(int a, int b, int c, int d, int e, int f, int g, int h) throws IOException {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(32);) {
            baos.write(toByteArray(a));
            baos.write(toByteArray(b));
            baos.write(toByteArray(c));
            baos.write(toByteArray(d));
            baos.write(toByteArray(e));
            baos.write(toByteArray(f));
            baos.write(toByteArray(g));
            baos.write(toByteArray(h));
            return baos.toByteArray();
        }
    }
 
    private static byte[] toByteArray(int i) {
        byte[] byteArray = new byte[4];
        byteArray[0] = (byte) (i >>> 24);
        byteArray[1] = (byte) ((i & 0xFFFFFF) >>> 16);
        byteArray[2] = (byte) ((i & 0xFFFF) >>> 8);
        byteArray[3] = (byte) (i & 0xFF);
        return byteArray;
    }
 
    private static String byteToHexString(byte[] bytes)
    {
        StringBuilder resultHexString = new StringBuilder();
        String tempStr;
        for (byte b: bytes) {
            //这里需要对b与0xff做位与运算,
            //若b为负数,强制转换将高位位扩展,导致错误,
            //故需要高位清零
            tempStr = Integer.toHexString(b & 0xff);
            //若转换后的十六进制数字只有一位,
            //则在前补"0"
            if (tempStr.length() == 1) {
                resultHexString.append(0).append(tempStr);
            } else {
                resultHexString.append(tempStr);
            }
        }
        return resultHexString.toString();
    }
 
    private Sm3Utils() {
    }
}           

3.4 Dependencies related to national secrets

<dependency>
                <groupId>org.bouncycastle</groupId>
                <artifactId>bcprov-jdk15on</artifactId>
                <version>1.57</version>
            </dependency>
            <dependency>
                <groupId>org.bouncycastle</groupId>
                <artifactId>bcprov-ext-jdk15on</artifactId>
                <version>1.57</version>
            </dependency>           

4. Log in to the Authentication section

4.1 Front-end part of the key code

1.npm installation of national secret:

npm install --save sm-crypto           

2. Quoting the state secret in vue:

import SMCRYPTO from "sm-crypto";           

3. Define SM2 in data:

data () {
    return {
      .
      .
      .
      sm2: SMCRYPTO.sm2,
      pubKey: "自己的SM2公钥"
    }
  },           

4. When logging in, use SM2 encryption to encrypt the account password and transfer it to the background

//账号密码使用sm2加密
          loginParams.account = this.sm2.doEncrypt(values.account, this.pubKey, 1);
          loginParams.password = this.sm2.doEncrypt(values.password, this.pubKey, 1);           

4.2 Key code for backend login login authentication

After receiving the account password of the front-end for processing, the account and password should be decrypted using the SM2 private key, and the decrypted password needs to be encrypted with SM3 and then salted to compare with the password ciphertext of the database:

//账号解密
        account = SM2Utils.decrypt(Sm2crypto.priKey,account,1);
 
        SysUser sysUser = sysUserService.getUserByCount(account);
 
        //用户不存在,账号或密码错误
        if (ObjectUtil.isEmpty(sysUser)) {
            LogManager.me().executeLoginLog(account, LogSuccessStatusEnum.FAIL.getCode(), AuthExceptionEnum.ACCOUNT_PWD_ERROR.getMessage());
            throw new AuthException(AuthExceptionEnum.ACCOUNT_PWD_ERROR);
        }
 
 
 
        String passwordBcrypt = sysUser.getPassword();
 
        //密码解密
        password = SM2Utils.decrypt(Sm2crypto.priKey,password,1);
 
        //获取用户表的盐
        byte[] mySalt = Base64.getDecoder().decode(sysUser.getSalt());
        //用密码sm3加密后再加盐,形成新的密码
        password = Hex.toHexString(Sm3crypto.pwdSaltedHashValue(mySalt,password));
 
        //验证账号密码是否正确
        if (ObjectUtil.isEmpty(passwordBcrypt) || !password.equals(passwordBcrypt)) {
             LogManager.me().executeLoginLog(sysUser.getAccount(),         LogSuccessStatusEnum.FAIL.getCode(), AuthExceptionEnum.ACCOUNT_PWD_ERROR.getMessage());
            throw new AuthException(AuthExceptionEnum.ACCOUNT_PWD_ERROR);
        }           

4.2.1.SM2 public and private key (Sm2crypto)

import cn.stylefeng.guns.core.sm2.SM2KeyPair;
import cn.stylefeng.guns.core.sm2.SM2Utils;
import org.springframework.stereotype.Component;
 
import java.lang.reflect.Field;
import java.util.Objects;
 
@Component
public class Sm2crypto {
 
    public static String pubKey = "自己的SM2公钥,与前端加密用的公钥一样";
    public static String priKey = "自己的SM2私钥";
 
}           

4.2.2SM2 Tools (SM2Utils)

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
 
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
 
@Slf4j
public class SM2Utils {
 
 
    /**
     * SM2加密算法
     * @param publicKey 公钥
     * @param data 待加密的数据
     * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
     */
    public static String encrypt(String publicKey, String data){
        // 按国密排序标准加密
        return encrypt(publicKey, data, SM2EngineExtend.CIPHERMODE_NORM);
    }
 
    /**
     * SM2加密算法
     * @param publicKey 公钥
     * @param data 待加密的数据
     * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;
     * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
     */
    public static String encrypt(String publicKey, String data, int cipherMode){
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
 
        SM2EngineExtend sm2Engine = new SM2EngineExtend();
        // 设置sm2为加密模式
        sm2Engine.init(true, cipherMode, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
 
        byte[] arrayOfBytes = null;
        try {
            byte[] in = data.getBytes();
            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        } catch (Exception e) {
            log.error("SM2加密时出现异常:{}", e.getMessage(), e);
        }
        return Hex.toHexString(arrayOfBytes);
    }
 
    /**
     * 获取sm2密钥对
     * BC库使用的公钥=64个字节+1个字节(04标志位),BC库使用的私钥=32个字节
     * SM2秘钥的组成部分有 私钥D 、公钥X 、 公钥Y , 他们都可以用长度为64的16进制的HEX串表示,
     * <br/>SM2公钥并不是直接由X+Y表示 , 而是额外添加了一个头,当启用压缩时:公钥=有头+公钥X ,即省略了公钥Y的部分
     * @param compressed 是否压缩公钥(加密解密都使用BC库才能使用压缩)
     * @return
     */
    public static SM2KeyPair getSm2Keys(boolean compressed){
        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //1.创建密钥生成器
        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
        //2.初始化生成器,带上随机数
        try {
            keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
        } catch (NoSuchAlgorithmException e) {
            log.error("生成公私钥对时出现异常:", e);
        }
        //3.生成密钥对
        AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
        ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters)asymmetricCipherKeyPair.getPublic();
        ECPoint ecPoint = publicKeyParameters.getQ();
        // 把公钥放入map中,默认压缩公钥
        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
        String publicKey = Hex.toHexString(ecPoint.getEncoded(compressed));
        ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
        BigInteger intPrivateKey = privateKeyParameters.getD();
        // 把私钥放入map中
        String privateKey = intPrivateKey.toString(16);
        return new SM2KeyPair(publicKey, privateKey);
    }
 
    /**
     * SM2解密算法
     * @param privateKey    私钥
     * @param cipherData    密文数据
     * @return
     */
    public static String decrypt(String privateKey, String cipherData) {
        // // 按国密排序标准解密
        return decrypt(privateKey, cipherData, SM2EngineExtend.CIPHERMODE_NORM);
    }
 
    /**
     * SM2解密算法
     * @param privateKey    私钥
     * @param cipherData    密文数据
     * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;
     * @return
     */
    public static String decrypt(String privateKey, String cipherData, int cipherMode) {
        // 使用BC库加解密时密文以04开头,传入的密文前面没有04则补上
        if (!cipherData.startsWith("04")){
            cipherData = "04" + cipherData;
        }
        byte[] cipherDataByte = Hex.decode(cipherData);
 
        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
 
        BigInteger privateKeyD = new BigInteger(privateKey, 16);
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
 
        SM2EngineExtend sm2Engine = new SM2EngineExtend();
        // 设置sm2为解密模式
        sm2Engine.init(false, cipherMode, privateKeyParameters);
 
        String result = "";
        try {
            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
            return new String(arrayOfBytes);
        } catch (Exception e) {
            log.error("SM2解密时出现异常:{}", e.getMessage(), e);
        }
        return result;
 
    }
 
}           

4.2.3SM2EngineExtend class

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.math.ec.ECConstants;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
 
import java.math.BigInteger;
import java.security.SecureRandom;
 
public class SM2EngineExtend {
 
    private final Digest digest;
 
    /**是否为加密模式*/
    private boolean forEncryption;
    private ECKeyParameters ecKey;
    private ECDomainParameters ecParams;
    private int curveLength;
    private SecureRandom random;
    /**密文排序方式*/
    private int cipherMode;
 
    /**BC库默认排序方式-C1C2C3*/
    public static int CIPHERMODE_BC = 0;
    /**国密标准排序方式-C1C3C2*/
    public static int CIPHERMODE_NORM = 1;
 
    public SM2EngineExtend() {
        this(new SM3Digest());
    }
 
    public SM2EngineExtend(Digest digest) {
        this.digest = digest;
    }
 
    /**
     * 设置密文排序方式
     * @param cipherMode
     */
    public void setCipherMode(int cipherMode){
        this.cipherMode = cipherMode;
    }
 
    /**
     * 默认初始化方法,使用国密排序标准
     * @param forEncryption - 是否以加密模式初始化
     * @param param - 曲线参数
     */
    public void init(boolean forEncryption, CipherParameters param) {
        init(forEncryption, CIPHERMODE_NORM, param);
    }
 
    /**
     * 默认初始化方法,使用国密排序标准
     * @param forEncryption 是否以加密模式初始化
     * @param cipherMode 加密数据排列模式:1-标准排序;0-BC默认排序
     * @param param 曲线参数
     */
    public void init(boolean forEncryption, int cipherMode, CipherParameters param) {
        this.forEncryption = forEncryption;
        this.cipherMode = cipherMode;
        if (forEncryption) {
            ParametersWithRandom rParam = (ParametersWithRandom) param;
 
            ecKey = (ECKeyParameters) rParam.getParameters();
            ecParams = ecKey.getParameters();
 
            ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
            if (s.isInfinity()) {
                throw new IllegalArgumentException("invalid key: [h]Q at infinity");
            }
 
            random = rParam.getRandom();
        } else {
            ecKey = (ECKeyParameters) param;
            ecParams = ecKey.getParameters();
        }
 
        curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8;
    }
 
    /**
     * 加密或解密输入数据
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    public byte[] processBlock( byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
        if (forEncryption) {
            // 加密
            return encrypt(in, inOff, inLen);
        } else {
            return decrypt(in, inOff, inLen);
        }
    }
 
    /**
     * 加密实现,根据cipherMode输出指定排列的结果,默认按标准方式排列
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    private byte[] encrypt(byte[] in, int inOff, int inLen)
            throws InvalidCipherTextException {
        byte[] c2 = new byte[inLen];
 
        System.arraycopy(in, inOff, c2, 0, c2.length);
 
        byte[] c1;
        ECPoint kPB;
        do {
            BigInteger k = nextK();
 
            ECPoint c1P = ecParams.getG().multiply(k).normalize();
 
            c1 = c1P.getEncoded(false);
 
            kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();
 
            kdf(digest, kPB, c2);
        }
        while (notEncrypted(c2, in, inOff));
 
        byte[] c3 = new byte[digest.getDigestSize()];
 
        addFieldElement(digest, kPB.getAffineXCoord());
        digest.update(in, inOff, inLen);
        addFieldElement(digest, kPB.getAffineYCoord());
 
        digest.doFinal(c3, 0);
        if (cipherMode == CIPHERMODE_NORM){
            return Arrays.concatenate(c1, c3, c2);
        }
        return Arrays.concatenate(c1, c2, c3);
    }
 
    /**
     * 解密实现,默认按标准排列方式解密,解密时解出c2部分原文并校验c3部分
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    private byte[] decrypt(byte[] in, int inOff, int inLen)
            throws InvalidCipherTextException {
        byte[] c1 = new byte[curveLength * 2 + 1];
 
        System.arraycopy(in, inOff, c1, 0, c1.length);
 
        ECPoint c1P = ecParams.getCurve().decodePoint(c1);
 
        ECPoint s = c1P.multiply(ecParams.getH());
        if (s.isInfinity()) {
            throw new InvalidCipherTextException("[h]C1 at infinity");
        }
 
        c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();
 
        byte[] c2 = new byte[inLen - c1.length - digest.getDigestSize()];
        if (cipherMode == CIPHERMODE_BC) {
            System.arraycopy(in, inOff + c1.length, c2, 0, c2.length);
        }else{
            // C1 C3 C2
            System.arraycopy(in, inOff + c1.length + digest.getDigestSize(), c2, 0, c2.length);
        }
 
        kdf(digest, c1P, c2);
 
        byte[] c3 = new byte[digest.getDigestSize()];
 
        addFieldElement(digest, c1P.getAffineXCoord());
        digest.update(c2, 0, c2.length);
        addFieldElement(digest, c1P.getAffineYCoord());
 
        digest.doFinal(c3, 0);
 
        int check = 0;
        // 检查密文输入值C3部分和由摘要生成的C3是否一致
        if (cipherMode == CIPHERMODE_BC) {
            for (int i = 0; i != c3.length; i++) {
                check |= c3[i] ^ in[c1.length + c2.length + i];
            }
        }else{
            for (int i = 0; i != c3.length; i++) {
                check |= c3[i] ^ in[c1.length + i];
            }
        }
 
        clearBlock(c1);
        clearBlock(c3);
 
        if (check != 0) {
            clearBlock(c2);
            throw new InvalidCipherTextException("invalid cipher text");
        }
 
        return c2;
    }
 
    private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {
        for (int i = 0; i != encData.length; i++) {
            if (encData[i] != in[inOff]) {
                return false;
            }
        }
 
        return true;
    }
 
    private void kdf(Digest digest, ECPoint c1, byte[] encData) {
        int ct = 1;
        int v = digest.getDigestSize();
 
        byte[] buf = new byte[digest.getDigestSize()];
        int off = 0;
 
        for (int i = 1; i <= ((encData.length + v - 1) / v); i++) {
            addFieldElement(digest, c1.getAffineXCoord());
            addFieldElement(digest, c1.getAffineYCoord());
            digest.update((byte) (ct >> 24));
            digest.update((byte) (ct >> 16));
            digest.update((byte) (ct >> 8));
            digest.update((byte) ct);
 
            digest.doFinal(buf, 0);
 
            if (off + buf.length < encData.length) {
                xor(encData, buf, off, buf.length);
            } else {
                xor(encData, buf, off, encData.length - off);
            }
 
            off += buf.length;
            ct++;
        }
    }
 
    private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
        for (int i = 0; i != dRemaining; i++) {
            data[dOff + i] ^= kdfOut[i];
        }
    }
 
    private BigInteger nextK() {
        int qBitLength = ecParams.getN().bitLength();
 
        BigInteger k;
        do {
            k = new BigInteger(qBitLength, random);
        }
        while (k.equals(ECConstants.ZERO) || k.compareTo(ecParams.getN()) >= 0);
 
        return k;
    }
 
    private void addFieldElement(Digest digest, ECFieldElement v) {
        byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger());
 
        digest.update(p, 0, p.length);
    }
 
    /**
     * clear possible sensitive data
     */
    private void clearBlock(
            byte[] block) {
        for (int i = 0; i != block.length; i++) {
            block[i] = 0;
        }
    }
 
 
}
 
            

Source: https://blog.csdn.net/qq_27387133/article/details/121425276