DEV Community

HarmonyOS
HarmonyOS

Posted on

How to Resolve the Issue of Inconsistent Length of SM2 Encrypted Text in National Cryptography Standards

Read the original article:How to Resolve the Issue of Inconsistent Length of SM2 Encrypted Text in National Cryptography Standards

Context

Resolving the Issue of Inconsistent SM2 Cipher Length in National Cryptography - Cipher Unparsable Problem

Description

When using SM2, the following issues are commonly encountered:

  1. The length of the ciphertext after SM2 encryption is uncertain, making it unparseable by the server.
  2. The ciphertext after SM2 encryption in ArkTS cannot be parsed by the server.

SM2 is a Chinese national standard elliptic curve public key cryptographic algorithm.

SM2 Ciphertext Structure

  • C1: A point on the elliptic curve, representing the temporary public key (including C1_X and C1_Y, i.e., XY components)
  • C2: The encrypted data (actual ciphertext
  • C3: A hash value used for integrity verification

SM2 Ciphertext Length

  • C1: A point (x,y) on the elliptic curve, with both X and Y coordinates being 256 bits (32 bytes), so C1 is usually 64 bytes (512 bits)
  • C2: The raw ciphertext, with a length consistent with the plaintext
  • C3: The hash value, generated by the SM2 using the SM3 hash algorithm, which is 256 bits, i.e., 32 bytes

Therefore, assuming the plaintext length is N bytes, the ciphertext length is 64 + N + 32 = N + 96. In production, a prefix '04' is usually added before the ciphertext to indicate that C1 is uncompressed. Thus, the actual ciphertext is generally N + 96 or N + 97.

SM2 Ciphertext Concatenation Standards

C1C2C3: Follows the Chinese national standard (GB/T 32918.2-2016)

C1C3C2: Follows the ISO/IEC 14888-3:2018 standard

Currently, ArkTS uses the C1C3C2 standard. C1, C2, and C3 are independent and can be re-concatenated to meet specific compatibility or performance requirements.

Solution / Approach

According to the section on SM2 ciphertext length in the background knowledge, the ciphertext length is typically fixed at plaintext length N+96 or N+97 bytes. However, in ArkTS, it was discovered that the ciphertext length could be one of the following: plaintext length + 95 bytes, plaintext length + 95.5 bytes, or plaintext length + 96 bytes. In fact, in ArkTS's SM2 encryption, to ensure security and prevent data from being intercepted during concatenation, the data returned by the underlying openssl is directly passed through. The data returned by the underlying openssl follows a specification where the high bit being 0 is not displayed by default, which does not affect the generation of the key.

Currently, most legacy programs maintain the use of N+96 or N+97 ciphertext lengths, and during parsing, they distinguish between C1, C2, and C3 by directly truncating to a fixed length, resulting in ArkTS-generated SM2 ciphertexts not being correctly parsed.

Additionally, in legacy programs, the ciphertext generally defaults to the concatenation order of C1C2C3, leading to ArkTS-generated SM2 ciphertexts not being correctly parsed.

Based on the above analysis, users can adapt to the following suitable methods according to the schemes of other existing programs, namely, the ciphertext length needs to be adjusted to a fixed length, and the concatenation order must comply with the standard.

The encryption process will not be elaborated here. It is assumed that pubKey is the public key, and res is the encrypted ciphertext:

 let pubKey = await this.convertStrToPubKey(pubKeyStr);
 let res = await this.encryptMessagePromise(pubKey, str);
Enter fullscreen mode Exit fullscreen mode

Preparation Before Adjustment

Whether it is length adaptation or splicing standard adaptation, users need to first parse to obtain C1 (temporary public key, including C1_X, C1_Y), C2 (ciphertext), and C3 (hash value).

Use cryptoFramework.SM2CryptoUtil.getCipherTextSpec to obtain cryptoFramework.SM2CipherTextSpec, which includes the following variables:

- xCoordinate: X component, i.e., C1_X

- yCoordinate: Y component, i.e., C1_Y

- cipherTextData: Raw ciphertext, i.e., C2

- hashData: Hash value, i.e., C3

Parse to obtain the hexadecimal numbers of the above ciphertext parameters:

 let spec: cryptoFramework.SM2CipherTextSpec = cryptoFramework.SM2CryptoUtil.getCipherTextSpec(res, 'C1C3C2');

 let C1x = spec.xCoordinate.toString(16);
 let C1y = spec.yCoordinate.toString(16);
 let C2 = buffer.from(spec.cipherTextData).toString('hex');
 let C3 = buffer.from(spec.hashData).toString('hex');
Enter fullscreen mode Exit fullscreen mode

Adjust the ciphertext length

Determine the lengths of the X and Y components. If the hexadecimal number length is less than 64 (i.e., 63), pad with zeros in the higher bits.

 C1x = (C1x.lenth == 64) ? C1x : ('0' + C1x);
 C1y = (C1y.lenth == 64) ? C1y : ('0' + C1y);
Enter fullscreen mode Exit fullscreen mode

Concatenated ciphertext

C1 includes C1_X and C1_Y, and C1 = C1_X + C1_Y:

 let C1 = C1x + C1y;
Enter fullscreen mode Exit fullscreen mode

You can combine the following items according to the standard of the inventory program:

 // To assemble into C1C2C3
 let result = C1 + C2 + C3;
 // To add a prefix indicating that C1 is uncompressed
 let result = '04' + C1 + C2 + C3;

 // To assemble into C1C3C2
 let result = C1 + C3 + C2;
Enter fullscreen mode Exit fullscreen mode

The final code is:

async encSM2C1C2C3(str: string, pubKeyStr = this.pubKeyStr) {
  let pubKey = await this.convertStrToPubKey(pubKeyStr)
  let res = await this.encryptMessagePromise(pubKey, str)
  let spec: cryptoFramework.SM2CipherTextSpec = cryptoFramework.SM2CryptoUtil.getCipherTextSpec(res, 'C1C3C2');
  let C1x = spec.xCoordinate.toString(16);
  let C1y = spec.yCoordinate.toString(16);
  let C2 = buffer.from(spec.cipherTextData).toString('hex');
  let C3 = buffer.from(spec.hashData).toString('hex');
  C1x = (C1x.lenth == 64) ? C1x : ('0' + C1x);
  C1y = (C1y.lenth == 64) ? C1y : ('0' + C1y);
  // Concatenate C1C2C3, and if a prefix is to be added, it indicates that C1 is uncompressed. 
  return C1x + C1y + C2 + C3
}
Enter fullscreen mode Exit fullscreen mode

In summary, after using the method SM2CryptoUtil.getCipherTextSpec, users can obtain all the ciphertext parameters of SM2. They can then adjust the ciphertext parameters in ArkTS according to the ciphertext usage behavior in the existing program and reassemble them accordingly.

Key Takeaways

  • ArkTS SM2 encryption produces ciphertext lengths that differ (N+95, N+95.5, N+96) from the legacy fixed lengths (N+96 or N+97), causing parsing incompatibilities.

Written by Dogan Evci

Top comments (0)