Practical application of domestic encryption: use SM3 salt to store the password, and use SM2 for login authentication

Keywords: Java Database security

catalogue

1. Brief

2. Development environment and tools

3. Background password encryption

3.1 encryption code

3.2 SM3 encryption class (Sm3crypto)

3.3 state secret SM3 tools (Sm3Utils)

3.4 dependence package related to state secrets

4. Login authentication

4.1 front end key codes

4.2 key codes of backend login authentication

4.2.1.SM2 public private key (Sm2crypto)  )

4.2.2 state secret SM2 tools (SM2Utils)

5. It's done!

1. Brief

        Combined with the actual personal code, this paper uses SM3 to salt the password and SM2 to log in and authenticate. It mainly needs to know the following two points:

1. When adding a new user, the plaintext password is encrypted by SM3 and then salted (randomly generated) to form a ciphertext and stored in the database. At the same time, we also need to store the salt in a field of the user table (used for password verification and comparison during login). When the user modifies, it is not necessary to update and store the salt.

2. During login authentication, the account and password are encrypted with sm2 through a fixed public key at the front end and then transmitted to the back end. The back end decrypts the account and password through a fixed private key. The decrypted password is encrypted with sm3 salt and compared with the password in the database for authentication.

3. The following codes are all key codes, not all of them, but enough for your reference to realize the function.

2. Development environment and tools

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

3. Background password encryption

The password plaintext is received in the background, encrypted and saved:

3.1 encryption code

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

        byte[] mySalt = Sm3crypto.getSalt();
        //Convert the salt into a string with base64 and store it in the database
        sysUser.setSalt(Base64.getEncoder().encodeToString(mySalt));
        //Encrypt with password sm3 and then add salt to form a new password
        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 encryption
public class Sm3crypto {


    public static byte[] getSalt(){
        /*
         * Randomly generate 128 bit random numbers
         */
        SecureRandom random = new SecureRandom();
        byte bytes1[] = new byte[16];
        random.nextBytes(bytes1);
        return bytes1;
    }

    public static byte[] pwdSaltedHashValue(byte[] bytes1, String passwdString) {

        //sm3 encryption password
        try {
            passwdString = Sm3Utils.encodeSM3(passwdString);
        } catch (IOException e) {
            e.printStackTrace();
        }

        /*
         * Salt: a combination of random numbers and passwords
         */
        byte passwdbyte[]= arraycat(bytes1,passwdString.getBytes());
        //SM3 calculation
        SM3Digest mdDigest=new SM3Digest();
        mdDigest.update(passwdbyte,0,passwdbyte.length);
        byte[] result=new byte[mdDigest.getDigestSize()];
        mdDigest.doFinal(result, 0);
        return result;
    }
    /*
     * Splicing buf1 and buf2 arrays
     */
    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 state secret SM3 tools (Sm3Utils)

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.Arrays;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * State secret SM3, message summary (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) {
            //Here, we need to do bit sum operation on b and 0xff,
            //If b is negative, the cast will expand the high bit, resulting in an error,
            //Therefore, it is necessary to clear the high position
            tempStr = Integer.toHexString(b & 0xff);
            //If the converted hexadecimal digit has only one digit,
            //Then fill "0" before
            if (tempStr.length() == 1) {
                resultHexString.append(0).append(tempStr);
            } else {
                resultHexString.append(tempStr);
            }
        }
        return resultHexString.toString();
    }

    private Sm3Utils() {
    }
}

3.4 dependence package related to state 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. Login authentication

4.1 front end key codes

1. National secrets for NPM installation:

npm install --save sm-crypto

2. State secrets quoted in Vue:

import SMCRYPTO from "sm-crypto";

3. Define the following sm2 in data:

data () {
    return {
      .
      .
      .
      sm2: SMCRYPTO.sm2,
      pubKey: "own SM2 Public key"
    }
  },

4. When logging in, the account and password are encrypted with sm2 and transmitted to the background

          //The account password is encrypted with sm2
          loginParams.account = this.sm2.doEncrypt(values.account, this.pubKey, 1);
          loginParams.password = this.sm2.doEncrypt(values.password, this.pubKey, 1);

4.2 key codes of backend login authentication

1. Process after receiving the front-end account and password. The account and password shall be decrypted with SM2 private key. The decrypted password shall be encrypted with sm3 and salted for processing, and then compared with the password of the database:

        //Account decryption
        account = SM2Utils.decrypt(Sm2crypto.priKey,account,1);

        SysUser sysUser = sysUserService.getUserByCount(account);

        //User does not exist, account or password error
        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 decryption
        password = SM2Utils.decrypt(Sm2crypto.priKey,password,1);

        //Gets the name of the user table
        byte[] mySalt = Base64.getDecoder().decode(sysUser.getSalt());
        //Encrypt with password sm3 and then add salt to form a new password
        password = Hex.toHexString(Sm3crypto.pwdSaltedHashValue(mySalt,password));

        //Verify whether the account password is correct
        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 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 = "own SM2 The public key is the same as the public key used for front-end encryption";
    public static String priKey = "own SM2 Private key";

}

4.2.2 state secret SM2 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 encryption algorithm 
     * @param publicKey Public key
     * @param data Data to be encrypted
     * @return Ciphertext: the ciphertext band generated by BC library is identified by 04. When docking with non BC library, the beginning 04 needs to be removed
     */
    public static String encrypt(String publicKey, String data){
        // Encryption by national secret sorting standard
        return encrypt(publicKey, data, SM2EngineExtend.CIPHERMODE_NORM);
    }

    /**
     * SM2 encryption algorithm 
     * @param publicKey Public key
     * @param data Data to be encrypted
     * @param cipherMode Ciphertext arrangement: 0-C1C2C3; 1-C1C3C2;
     * @return Ciphertext: the ciphertext band generated by BC library is identified by 04. When docking with non BC library, the beginning 04 needs to be removed
     */
    public static String encrypt(String publicKey, String data, int cipherMode){
        // Get a SM2 curve parameter
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // Construct ECC algorithm parameters, curve equation, elliptic curve G-point and large integer N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //Extract public key points
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 02 or 03 in front of the public key indicates that it is a compressed public key, 04 indicates an uncompressed public key, and 04 can be removed
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);

        SM2EngineExtend sm2Engine = new SM2EngineExtend();
        // Set sm2 to encryption mode
        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 Exception occurred during encryption:{}", e.getMessage(), e);
        }
        return Hex.toHexString(arrayOfBytes);
    }

    /**
     * Get sm2 key pair
     * BC The public key used by the library = 64 bytes + 1 byte (04 flag bit), and the private key used by the BC Library = 32 bytes
     * SM2 The components of the secret key include private key D, public key X and public key Y, which can be represented by a HEX string with a length of 64,
     * <br/>SM2 The public key is not directly represented by X+Y, but an additional header is added. When compression is enabled: public key = header + public key x, that is, the part of public key Y is omitted
     * @param compressed Whether to compress the public key (only BC library can be used for encryption and decryption)
     * @return
     */
    public static SM2KeyPair getSm2Keys(boolean compressed){
        //Get a SM2 curve parameter
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        //Construct domain parameters
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //1. Create a key generator
        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
        //2. Initialize the generator with random numbers
        try {
            keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
        } catch (NoSuchAlgorithmException e) {
            log.error("An exception occurred while generating a public-private key pair:", e);
        }
        //3. Generate key pair
        AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
        ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters)asymmetricCipherKeyPair.getPublic();
        ECPoint ecPoint = publicKeyParameters.getQ();
        // Put the public key into the map and compress the public key by default
        // 02 or 03 in front of the public key indicates that it is a compressed public key, 04 indicates an uncompressed public key, and 04 can be removed
        String publicKey = Hex.toHexString(ecPoint.getEncoded(compressed));
        ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
        BigInteger intPrivateKey = privateKeyParameters.getD();
        // Put the private key into the map
        String privateKey = intPrivateKey.toString(16);
        return new SM2KeyPair(publicKey, privateKey);
    }

    /**
     * SM2 Decryption algorithm
     * @param privateKey    Private key
     * @param cipherData    Ciphertext data
     * @return
     */
    public static String decrypt(String privateKey, String cipherData) {
        // //Decryption by state secret sorting standard
        return decrypt(privateKey, cipherData, SM2EngineExtend.CIPHERMODE_NORM);
    }

    /**
     * SM2 Decryption algorithm
     * @param privateKey    Private key
     * @param cipherData    Ciphertext data
     * @param cipherMode Ciphertext arrangement: 0-C1C2C3; 1-C1C3C2;
     * @return
     */
    public static String decrypt(String privateKey, String cipherData, int cipherMode) {
        // When using the BC library for encryption and decryption, the ciphertext starts with 04, and if there is no 04 in front of the incoming ciphertext, fill it in
        if (!cipherData.startsWith("04")){
            cipherData = "04" + cipherData;
        }
        byte[] cipherDataByte = Hex.decode(cipherData);

        //Get a SM2 curve parameter
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        //Construct domain parameters
        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();
        // Set sm2 to decryption mode
        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 Exception occurred during decryption:{}", e.getMessage(), e);
        }
        return result;

    }

}

5. It's done!

Posted by Liz_SA on Fri, 19 Nov 2021 14:20:10 -0800