This guide explains how to use Chinese national cryptographic standards (GM/T) with the ssh2 library.
The ssh2 library now supports the following Chinese national cryptographic algorithms:
| Algorithm | Type | Standard | Description |
|---|---|---|---|
sm2kep-sha3-sm3 |
Key Exchange | GM/T 0003-2012 | SM2 Elliptic Curve Diffie-Hellman Key Exchange with SM3 hash |
ssh-sm2 |
Host Key / Signature | GM/T 0003-2012 | SM2 signature with SM3 hash |
sm4-ctr |
Cipher | GM/T 0004-2012 | SM4 block cipher in CTR mode |
sm4-cbc |
Cipher | GM/T 0004-2012 | SM4 block cipher in CBC mode |
hmac-sm3 |
MAC | GM/T 0004-2016 | HMAC with SM3 hash |
hmac-sm3-etm@openssh.com |
MAC | GM/T 0004-2016 | HMAC-SM3 with Encrypt-then-MAC |
- Node.js >= 16 with OpenSSL 1.1.1+ (Node.js 18+ recommended for native SM2 support)
- No native compilation required — ssh2 is pure JavaScript
- Node.js 16 users: Install
sm-polyfillfor full SM2 support:npm install sm-polyfill
| Node.js Version | OpenSSL Version | SM2 Sign/Verify | SM3/SM4 | SM2 ECDH |
|---|---|---|---|---|
| 18+ | 3.0+ | ✅ Native | ✅ | ✅ |
| 16 | 1.1.1 | ✅ With sm-polyfill |
✅ | ✅ |
With sm-polyfill installed, Node.js 16 has full SM2/SM3/SM4 support - identical to Node.js 18+.
The polyfill is automatically loaded when:
- Node.js native SM2 sign/verify is not available (Node.js <=16)
- The
sm-polyfillpackage is installed
No code changes needed - just npm install sm-polyfill and everything works.
const constants = require('ssh2/lib/protocol/constants.js');
console.log('SM2 supported:', constants.sm2Supported);
console.log('SM3 supported:', constants.sm3Supported);
console.log('SM4 supported:', constants.sm4Supported);
// Or check directly:
const crypto = require('crypto');
console.log('OpenSSL version:', process.versions.openssl);const keygen = require('ssh2/lib/keygen.js');
// Generate SM2 host key
const hostKey = keygen.generateKeyPairSync('sm2', {
comment: 'my-sm2-host-key',
});
// Generate SM2 user key
const userKey = keygen.generateKeyPairSync('sm2', {
comment: 'my-sm2-user-key',
});
console.log('Host private key:');
console.log(hostKey.private); // OpenSSH format PEM string
console.log('User public key:');
console.log(userKey.public); // OpenSSH format public key stringconst { Server } = require('ssh2');
const keygen = require('ssh2/lib/keygen.js');
// Generate or load SM2 host key
const hostKeyResult = keygen.generateKeyPairSync('sm2', {
comment: 'server-host-key',
});
const server = new Server({
// Host key (raw PEM string or Buffer)
hostKeys: [hostKeyResult.private],
// Force GM algorithms only
algorithms: {
kex: ['sm2kep-sha3-sm3'], // SM2 key exchange
serverHostKey: ['ssh-sm2'], // SM2 host key
cipher: ['sm4-ctr', 'sm4-cbc'], // SM4 ciphers
hmac: ['hmac-sm3-etm@openssh.com', 'hmac-sm3'], // SM3 MAC
},
}, (client) => {
client.on('authentication', (ctx) => {
// Handle authentication
if (ctx.method === 'publickey' && ctx.username === 'admin') {
// ctx.key is { algo, data } - compare raw SSH key data
if (ctx.key.algo === 'ssh-sm2' &&
ctx.key.data.equals(expectedPubKeyBuffer)) {
ctx.accept();
return;
}
}
if (ctx.method === 'password' && ctx.username === 'admin') {
ctx.accept();
return;
}
ctx.reject(['publickey', 'password']);
});
client.on('session', (accept) => {
const session = accept();
session.on('exec', (accept, reject, info) => {
const channel = accept();
channel.write(`Executing: ${info.command}\n`);
channel.exit(0);
channel.end();
});
});
});
server.listen(2222, () => {
console.log('GM SSH server listening on port 2222');
});const { Client } = require('ssh2');
const fs = require('fs');
const client = new Client();
client.connect({
host: '127.0.0.1',
port: 2222,
username: 'admin',
// SM2 private key (PEM string or Buffer)
privateKey: fs.readFileSync('/path/to/sm2_user_key'),
// Force GM algorithms
algorithms: {
kex: ['sm2kep-sha3-sm3'],
serverHostKey: ['ssh-sm2'],
cipher: ['sm4-ctr'], // or 'sm4-cbc'
hmac: ['hmac-sm3-etm@openssh.com'], // or 'hmac-sm3'
},
// Verify host key fingerprint
hostVerifier: (fingerprint) => {
// Compare with expected SM2 host key fingerprint
console.log('Host key fingerprint:', fingerprint);
return true; // Accept for demo purposes
},
});
client.on('ready', () => {
console.log('Connected with SM2/SM3/SM4!');
client.exec('whoami', (err, stream) => {
stream.on('data', (data) => {
console.log(data.toString());
});
stream.on('close', () => {
client.end();
});
});
});
client.on('error', (err) => {
console.error('Connection error:', err.message);
});You can configure the server/client to support both GM and standard algorithms:
const server = new Server({
hostKeys: [
sm2HostKey, // SM2 host key
rsaHostKey, // RSA host key (fallback)
],
algorithms: {
kex: [
'sm2kep-sha3-sm3', // Preferred: SM2
'curve25519-sha256', // Fallback: standard
'ecdh-sha2-nistp256',
],
serverHostKey: [
'ssh-sm2', // Preferred: SM2
'ssh-ed25519', // Fallback: standard
'rsa-sha2-512',
],
cipher: [
'sm4-ctr', // Preferred: SM4
'aes256-gcm@openssh.com', // Fallback: standard
'aes128-ctr',
],
hmac: [
'hmac-sm3-etm@openssh.com', // Preferred: SM3
'hmac-sha2-256-etm@openssh.com',
],
},
}, ...);client.connect({
// ...
algorithms: {
// Specify preferred order
kex: ['sm2kep-sha3-sm3', 'curve25519-sha256'],
serverHostKey: ['ssh-sm2', 'ssh-ed25519'],
cipher: ['sm4-ctr', 'aes256-gcm@openssh.com'],
hmac: ['hmac-sm3-etm@openssh.com', 'hmac-sha2-256'],
},
});const { parseKey } = require('ssh2/lib/protocol/keyParser.js');
const fs = require('fs');
// Parse private key
const privateKey = parseKey(fs.readFileSync('/path/to/sm2_key'));
if (privateKey instanceof Error) {
console.error('Failed to parse key:', privateKey.message);
} else {
console.log('Key type:', privateKey.type); // 'ssh-sm2'
console.log('Comment:', privateKey.comment);
}
// Parse public key
const publicKey = parseKey(fs.readFileSync('/path/to/sm2_key.pub'));const { parseKey } = require('ssh2/lib/protocol/keyParser.js');
const keygen = require('ssh2/lib/keygen.js');
const keyResult = keygen.generateKeyPairSync('sm2');
const privateKey = parseKey(keyResult.private);
const publicKey = parseKey(keyResult.public);
const data = Buffer.from('data to sign');
// Sign
const signature = privateKey.sign(data, 'sm3');
console.log('Signature:', signature.toString('hex'));
// Verify
const verified = publicKey.verify(data, signature, 'sm3');
console.log('Verified:', verified); // trueconst { parseKey } = require('ssh2/lib/protocol/keyParser.js');
const keygen = require('ssh2/lib/keygen.js');
// Generate key
const keyResult = keygen.generateKeyPairSync('sm2', {
comment: 'my-key',
});
// Parse and get different formats
const privateKey = parseKey(keyResult.private);
console.log('OpenSSH PEM:');
console.log(privateKey.getPrivatePEM());
console.log('SSH public key:');
console.log(privateKey.getPublicSSH().toString('base64'));A Docker test server is provided in temp/docker/sm-ssh/:
cd temp/docker/sm-ssh
# Build and start
docker-compose up --build
# The server runs on port 2222 with SM2 host key
# Use SM2 client keys from temp/docker/sm-ssh/keys/The Docker server (temp/docker/sm-ssh/server.js) demonstrates:
- SM2 host key authentication
- SM2 key exchange (sm2kep-sha3-sm3)
- SM4 cipher (sm4-ctr, sm4-cbc)
- SM3 MAC (hmac-sm3, hmac-sm3-etm@openssh.com)
- Password and publickey authentication
Run the SM2/SM3/SM4 integration tests:
node test/integration/test-sm-ssh.jsTests include:
- SM2 key generation and parsing
- SM2 ECDH key exchange
- SM4 cipher round-trip
- SM3 hash and HMAC (with known test vectors)
- SSH handshake with various algorithm combinations
- SSH exec command over GM connection
- SSH password authentication over GM connection
Check your Node.js version and OpenSSL version:
node -e "console.log('Node:', process.version); console.log('OpenSSL:', process.versions.openssl)"If on Node.js 16: Install sm-polyfill for full SM2 support:
npm install sm-polyfillIf on Node.js 18+: All SM algorithms are natively supported. If you still see this error, ensure you are using the latest version of ssh2.
If you don't want to install sm-polyfill, you can still use SM3/SM4 on Node.js 16 by using a non-SM2 host key algorithm:
const { Server } = require('ssh2');
const fs = require('fs');
// Use ed25519 or RSA for host key instead of SM2
const server = new Server({
hostKeys: [fs.readFileSync('/path/to/ed25519_host_key')],
algorithms: {
// SM2 key exchange still works on Node.js 16
kex: ['sm2kep-sha3-sm3', 'curve25519-sha256'],
// Use ed25519 for host key (not SM2)
serverHostKey: ['ssh-ed25519', 'ssh-sm2'],
// SM4 ciphers work on Node.js 16
cipher: ['sm4-ctr', 'aes256-gcm@openssh.com'],
// SM3 MAC works on Node.js 16
hmac: ['hmac-sm3-etm@openssh.com', 'hmac-sha2-256'],
},
}, ...);This gives you SM3 MAC and SM4 cipher encryption while using ed25519 for host key authentication.
The privateKey option in client.connect() must be a string or Buffer (PEM format), not a parsed key object:
// Correct
client.connect({
privateKey: fs.readFileSync('/path/to/key'), // Buffer
// or
privateKey: pemString, // String
});
// Wrong
const parsedKey = parseKey(fs.readFileSync('/path/to/key'));
client.connect({
privateKey: parsedKey, // Error: parsed key object
});Check that:
- The client's public key matches the server's expected key
- The algorithm names match (
ssh-sm2for SM2 keys) - The server accepts the authentication method
When connecting, you must verify the host key:
client.connect({
// ...
hostVerifier: (fingerprint) => {
// Compare with expected fingerprint
return fingerprint === expectedFingerprint;
},
});SM2 keys in SSH format do NOT include a curve name field (unlike ECDSA):
Public key:
string "ssh-sm2"
string Q (uncompressed point, 65 bytes: 0x04 + X(32) + Y(32))
Private key (OpenSSH format):
string "ssh-sm2"
string Q (public key point)
string d (private key scalar)
This is similar to ed25519 keys, which also omit the curve name.
- GM/T 0003-2012: SM2 Elliptic Curve Public Key Cryptography Algorithm
- GM/T 0004-2012: SM3 Cryptographic Hash Algorithm
- GM/T 0002-2012: SM4 Block Cipher Algorithm
- GM/T 0004-2016: SM4 Cryptographic Application Specification
- RFC 4251: SSH Protocol Architecture
- RFC 4253: SSH Transport Layer Protocol
- RFC 4252: SSH Authentication Protocol