天天看點

國産加密運用:使用SM3加鹽存儲密碼,并且使用SM2進行登入認證

1.簡要

本文結合個人實際代碼,使用SM3加鹽存儲密碼,并且使用SM2進行登入認證。主要有以下兩個點需要了解:

1.新增使用者時,将明文密碼通過SM3加密後再加鹽(随機生成)後形成密文存儲在資料庫中,同時我們也要将鹽存在使用者表的一個字段中(用于登入時密碼的校驗比對)。使用者修改時不需要将鹽進行更新存儲。

2.登入認證時,在前端通過一個固定的公鑰将賬号和密碼使用sm2進行加密後再傳輸到後端,後端通過一個固定的私鑰将賬号及密碼進行解密,解密後的密碼通過sm3加鹽進行加密後與資料庫中的密文進行比對認證。

3.以下所放的代碼均為關鍵代碼,并非全部代碼,但足以供大家參考實作功能。

2.開發環境及工具

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

3.背景密碼加密部分

背景接收到密碼明文,進行加密并儲存。

3.1加密代碼

使用者表(SysUser),密碼字段(password),放鹽的字段(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加密類(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工具類(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國密相關依賴包

<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.登入認證部分

4.1前端部分關鍵代碼

1.npm安裝國密:

npm install --save sm-crypto           

2.vue中引用國密:

import SMCRYPTO from "sm-crypto";           

3.在data中定義一下sm2:

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

4.登入時将賬号密碼使用sm2加密後傳輸到背景

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

4.2後端login登入認證的關鍵代碼

接收到前端的賬号密碼後進行處理,要将賬号和密碼利用SM2私鑰進行解密,解密後的密碼需要用sm3加密再加鹽進行處理後與資料庫的密碼密文進行比對:

//賬号解密
        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公私鑰(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工具類(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類

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

來源:https://blog.csdn.net/qq_27387133/article/details/121425276