2024-09-20 15:46:04 +02:00
/**
* @author flakjacket95 [dflack95@gmail.com]
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs" ;
import OperationError from "../errors/OperationError.mjs" ;
import { fromHex } from "../lib/Hex.mjs" ;
import { toBase64 } from "../lib/Base64.mjs" ;
import Utils from "../Utils.mjs" ;
import Sm3 from "crypto-api/src/hasher/sm3.mjs" ;
import { toHex } from "crypto-api/src/encoder/hex.mjs" ;
//import { ECCurveFp } from "jsrsasign";
import r from "jsrsasign" ;
/**
* SM2 Encrypt operation
*/
class SM2Encrypt extends Operation {
/**
* SM2Encrypt constructor
*/
constructor () {
super ();
this . name = "SM2 Encrypt" ;
this . module = "Ciphers" ;
this . description = "Encrypts a message utilizing the SM2 standard" ;
this . infoURL = "" ; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc)
this . inputType = "ArrayBuffer" ;
this . outputType = "string" ;
this . args = [
{
name : "Public Key X" ,
type : "string" ,
value : "DEADBEEF"
},
{
name : "Public Key Y" ,
type : "string" ,
value : "DEADBEEF"
},
{
"name" : "Output Format" ,
"type" : "option" ,
"value" : [ "C1C3C2" , "C1C2C3" ]
},
{
name : "Curve" ,
type : "option" ,
"value" : [ "sm2p256v1" ]
}
];
this . ecParams = null ;
this . rng = new r . SecureRandom ();
/*
For any additional curve definitions utilized by SM2, add another block like the below for that curve, then add the curve name to the Curve selection dropdown
*/
r . crypto . ECParameterDB . regist (
'sm2p256v1' , // name / p = 2**256 - 2**224 - 2**96 + 2**64 - 1
256 ,
'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF' , // p
'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC' , // a
'28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93' , // b
'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123' , // n
'1' , // h
'32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7' , // gx
'BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0' , // gy
[]
) // alias
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
run ( input , args ) {
const [ privateKeyX , privateKeyY , outputFormat , curveName ] = args ;
this . outputFormat = outputFormat ;
this . ecParams = r . crypto . ECParameterDB . getByName ( curveName );
2024-09-20 16:20:15 +02:00
/*
* TODO: This needs some additional length validation; and checking for errors in the decoding process
* TODO: Can probably support other public key encoding methods here as well in the future
*/
2024-09-20 15:46:04 +02:00
this . publicKey = this . ecParams . curve . decodePointHex ( "04" + privateKeyX + privateKeyY );
if ( this . publicKey . isInfinity ()) {
throw new OperationError ( "Invalid Public Key" );
}
var result = this . encrypt ( new Uint8Array ( input ))
return result
}
/**
* Highlight SM2 Encrypt
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlight ( pos , args ) {
const [ privateKeyX , privateKeyY , outputFormat , curveName ] = args ;
var num = pos [ 0 ]. end - pos [ 0 ]. start
var adjust = 128
if ( outputFormat == "C1C3C2" ) {
adjust = 192
}
pos [ 0 ]. start = Math . ceil ( pos [ 0 ]. start + adjust );
pos [ 0 ]. end = Math . floor ( pos [ 0 ]. end + adjust + num );
return pos ;
}
2024-09-20 16:20:15 +02:00
/**
* Main encryption function; takes user input, processes encryption and returns the result in hex (with the components arranged as configured by the user args)
*
* @param {*} input
* @returns {string}
*/
2024-09-20 15:46:04 +02:00
encrypt ( input ) {
const G = this . ecParams . G
2024-09-20 16:20:15 +02:00
/*
* Compute a new, random public key along the same elliptic curve to form the starting point for our encryption process (record the resulting X and Y as hex to provide as part of the operation output)
* k: Randomly generated BigInteger
* c1: Result of dotting our curve generator point `G` with the value of `k`
*/
2024-09-20 15:46:04 +02:00
var k = this . generatePublicKey ();
var c1 = G . multiply ( k );
2024-09-20 16:20:15 +02:00
const [ hexC1X , hexC1Y ] = this . getPointAsHex ( c1 );
2024-09-20 15:46:04 +02:00
const p2 = this . publicKey . multiply ( k );
2024-09-20 16:20:15 +02:00
/*
* Compute the C3 SM3 hash before we transform the array
*/
2024-09-20 15:46:04 +02:00
var c3 = this . c3 ( p2 , input );
2024-09-20 16:20:15 +02:00
/*
* Genreate a proper length encryption key, XOR iteratively, and convert newly encrypted data to hex
*/
2024-09-20 15:46:04 +02:00
var key = this . kdf ( p2 , input . byteLength );
for ( let i = 0 ; i < input . byteLength ; i ++ ) {
input [ i ] ^= Utils . ord ( key [ i ]);
}
var c2 = Buffer . from ( input ). toString ( 'hex' );
2024-09-20 16:20:15 +02:00
/*
* Check user input specs; order the output components as selected
*/
2024-09-20 15:46:04 +02:00
if ( this . outputFormat == "C1C3C2" ) {
return hexC1X + hexC1Y + c3 + c2 ;
} else {
return hexC1X + hexC1Y + c2 + c3 ;
}
}
2024-09-20 16:20:15 +02:00
/**
* Generates a large random number
*
* @param {*} limit
* @returns
*/
2024-09-20 15:46:04 +02:00
getBigRandom ( limit ) {
return new r . BigInteger ( limit . bitLength (), this . rng )
. mod ( limit . subtract ( r . BigInteger . ONE ))
. add ( r . BigInteger . ONE );
}
2024-09-20 16:20:15 +02:00
/**
* Helper function for generating a large random K number; utilized for generating our initial C1 point
* TODO: Do we need to do any sort of validation on the resulting k values?
*
* @returns {BigInteger}
*/
2024-09-20 15:46:04 +02:00
generatePublicKey () {
const n = this . ecParams . n ;
var k = this . getBigRandom ( n );
return k ;
}
2024-09-20 16:20:15 +02:00
/**
* SM2 Key Derivation Function (KDF); Takes P2 point, and generates a key material stream large enough to encrypt all of the input data
*
* @param {*} p2
* @param {*} len
* @returns {string}
*/
2024-09-20 15:46:04 +02:00
kdf ( p2 , len ) {
2024-09-20 16:20:15 +02:00
const [ hX , hY ] = this . getPointAsHex ( p2 );
2024-09-20 15:46:04 +02:00
var total = Math . ceil ( len / 32 ) + 1 ;
var cnt = 1 ;
var keyMaterial = ""
while ( cnt < total ) {
var num = Utils . intToByteArray ( cnt , 4 , "big" );
var overall = fromHex ( hX ). concat ( fromHex ( hY )). concat ( num )
keyMaterial += this . sm3 ( overall );
cnt ++ ;
}
return keyMaterial
}
2024-09-20 16:20:15 +02:00
/**
* Calculates the C3 component of our final encrypted payload; which is the SM3 hash of the P2 point and the original, unencrypted input data
*
* @param {*} p2
* @param {*} input
* @returns {string}
*/
2024-09-20 15:46:04 +02:00
c3 ( p2 , input ) {
2024-09-20 16:20:15 +02:00
const [ hX , hY ] = this . getPointAsHex ( p2 );
2024-09-20 15:46:04 +02:00
var overall = fromHex ( hX ). concat ( Array . from ( input )). concat ( fromHex ( hY ));
return toHex ( this . sm3 ( overall ));
}
2024-09-20 16:20:15 +02:00
/**
* SM3 setup helper function; takes input data as an array, processes the hash and returns the result
*
* @param {*} data
* @returns {string}
*/
2024-09-20 15:46:04 +02:00
sm3 ( data ) {
var hashData = Utils . arrayBufferToStr ( Uint8Array . from ( data ). buffer , false );
const hasher = new Sm3 ();
hasher . update ( hashData );
return hasher . finalize ();
}
2024-09-20 16:20:15 +02:00
/**
* Utility function, returns an elliptic curve points X and Y values as hex;
*
* @param {EcPointFp} point
* @returns {[]}
*/
getPointAsHex ( point ) {
var biX = point . getX (). toBigInteger ();
var biY = point . getY (). toBigInteger ();
var charlen = this . ecParams . keycharlen ;
var hX = ( "0000000000" + biX . toString ( 16 )). slice ( - charlen );
var hY = ( "0000000000" + biY . toString ( 16 )). slice ( - charlen );
return [ hX , hY ]
}
2024-09-20 15:46:04 +02:00
}
export default SM2Encrypt ;