Skip to content

Commit ab9637e

Browse files
author
Ricky
committed
- Updated gitignore file
- Updated README for Python project - Completed Node.js project - Created README for Node.js project
1 parent efc1db0 commit ab9637e

File tree

9 files changed

+1792
-71
lines changed

9 files changed

+1792
-71
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ idea/
66
__pycache__/
77

88
# JetBrains IDE
9-
.idea/
9+
.idea/
10+
11+
node_modules/

js/README.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# 🛡️ DLMS Crypto Tool
2+
3+
**DLMS Crypto Tool** is a Node.js package and command-line interface (CLI) utility for encrypting, decrypting, generate encryption key, and authenticating DLMS APDU messages.
4+
5+
## ✨ Features
6+
7+
- **🔒 Encryption:** Encrypts DLMS APDU messages.
8+
- **🔑 Decryption:** Decrypts APDU messages.
9+
- **🧾 Authentication:** Generates authentication tags.
10+
- **💻 CLI Interface:** Interact with the tool via a command‑line utility (`dlmscli`).
11+
- **📁 File I/O Support:** Optionally load input data from files and output results to files.
12+
- **📝 Verbose Mode:** Provides detailed logging for debugging and traceability.
13+
- **✅ Well-Tested:** Unit tests are provided with Mocha to ensure core functionality.
14+
15+
## 🚀 Requirements
16+
17+
- Node.js vs23.11.0 (tested only with this version)
18+
- [Commander](https://www.npmjs.com/package/commander)
19+
- [Mocha](https://mochajs.org/) if you want run Unit tests
20+
- npm (comes with Node.js)
21+
22+
## 📦 Installation
23+
24+
You can install the package globally from npm to get the CLI:
25+
26+
```
27+
npm install -g dlms_crypto_tool
28+
```
29+
30+
Or install it as a dependecy in your project:
31+
32+
```
33+
npm install dlms_crypto_tool
34+
```
35+
36+
## How to use it
37+
38+
The package provide a CLI command called `dlmscli`.
39+
40+
🔑 Generate a Random Encryption Key
41+
42+
```
43+
dlmscli key
44+
```
45+
46+
🔐 Encrypt an APDU Message
47+
48+
System Title = 5249435249435249\
49+
Frame Counter = 80000001\
50+
Encryption Key = 454E4352595054494F4E4B45594B4559\
51+
Authentication Key = 41555448454E5449434154494F4E4B45\
52+
APDU = c001810001000060010aff0200
53+
54+
```
55+
dlmscli encrypt 5249435249435249 80000001 454E4352595054494F4E4B45594B4559 41555448454E5449434154494F4E4B45 c001810001000060010aff0200
56+
```
57+
*Tip:* Use --infile <path> to load input from a file and --outfile <path> to save the output
58+
59+
🔓 Decrypt an APDU Message
60+
61+
System Title = 5249435249435249\
62+
Frame Counter = 80000001\
63+
Encryption Key = 454E4352595054494F4E4B45594B4559\
64+
Authentication Key = 41555448454E5449434154494F4E4B45\
65+
APDU = 0de63f2331a09aa85e8830f5f3
66+
67+
```
68+
dlmscli decrypt 5249435249435249 80000001 454E4352595054494F4E4B45594B4559 41555448454E5449434154494F4E4B45 0de63f2331a09aa85e8830f5f3
69+
```
70+
Result
71+
```
72+
Encrypted/Decrypted APDU: c001810001000060010aff0200
73+
```
74+
75+
🔎 Authenticate an APDU Message
76+
77+
System Title = 5249435249435249\
78+
Frame Counter = 00000001\
79+
Encryption Key = 454E4352595054494F4E4B45594B4559\
80+
Authentication Key = 41555448454E5449434154494F4E4B45\
81+
APDU = 0de63f2331a09aa85e8830f5f3
82+
```
83+
dlmscli auth 5249435249435249 00000001 454E4352595054494F4E4B45594B4559 41555448454E5449434154494F4E4B45 0de63f2331a09aa85e8830f5f3
84+
```
85+
Result
86+
```
87+
TAG: 62d423292e0fe5320370881d
88+
```
89+
90+
## 🧪 Running Tests
91+
92+
To run unit test using Mocha, follow these steps:
93+
94+
1. Clone the repository and install the development dependencies:
95+
96+
```
97+
git clone https://github.com/YourUsername/dlms-crypto-tool.git
98+
cd dlms-crypto-tool
99+
npm install
100+
```
101+
2. Run tests:
102+
103+
```
104+
105+
npm test
106+
```
107+
108+
## 🤝 Contributing
109+
110+
Contributions are welcome! To contribute:
111+
112+
1. Fork the repository.
113+
114+
2. Create a new branch for your changes.
115+
116+
3. Write tests for your modifications.
117+
118+
4. Submit a pull request with a detailed explanation of your changes.
119+
120+
## 📜 License
121+
122+
This project is licensed under the GNU General Public License v3.0

js/bin/dlmscli.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const crypto = require("crypto");
2+
const {program} = require("commander");
3+
const fs = require("fs");
4+
const {encryptAPDU, decryptAPDU, authAPDU} = require("../lib/dlmsCryptoTool");
5+
6+
program
7+
.version('0.0.1')
8+
.description('CLI tool for encrypting, decrypting, and authenticating DLMS APDU');
9+
10+
program
11+
.command('key')
12+
.description('Generate a random 16 bytes encryption key')
13+
.action(() => {
14+
const key = crypto.randomBytes(16).toString('hex');
15+
console.log(`Encryption Key: ${key}`)
16+
});
17+
18+
program
19+
.command('encrypt <system_title> <frame_counter> <encryption_key> <aad> <apdu>')
20+
.option('--infile <path>', 'Read APDU from a file')
21+
.option('--outfile <path>', 'Write output to a file')
22+
.option('-v, --verbose', 'Enables verbose mode')
23+
.description('Encrypt an APDU message')
24+
.action((system_title, frame_counter, encryption_key, aad, apdu, options) => {
25+
26+
if (options.infile) {
27+
28+
apdu = fs.readFileSync(options.infile, 'utf8').trim();
29+
if (options.verbose) console.log(`Read APDU from ${options.infile}`);
30+
}
31+
32+
const result = encryptAPDU(system_title, frame_counter, encryption_key, aad, apdu);
33+
// Split ciphertext & tag (tag is last 32 hex characters corresponding to 16 bytes).
34+
const ciphertext = result.slice(0, result.length - 32);
35+
const tag = result.slice(result.length - 32);
36+
const output = `Encrypted APDU: ${ciphertext}\nAuthentication TAG: ${tag}`;
37+
38+
if (options.outfile) {
39+
40+
fs.writeFileSync(options.outfile, output);
41+
if (options.verbose) console.log(`Written output to ${options.outfile}`);
42+
43+
} else {
44+
45+
console.log(output);
46+
47+
}
48+
49+
});
50+
51+
program
52+
.command('decrypt <system_title> <frame_counter> <encryption_key> <aad> <ciphertext>')
53+
.option('--infile <path>', 'Read ciphertext from a file')
54+
.option('--outfile <path>', 'Write output to a file')
55+
.description('Decrypt an APDU message')
56+
.action((system_title, frame_counter, encryption_key, aad, ciphertext, options) => {
57+
if (options.infile) {
58+
ciphertext = fs.readFileSync(options.infile, 'utf8').trim();
59+
}
60+
try {
61+
const plaintext = decryptAPDU(system_title, frame_counter, encryption_key, aad, ciphertext);
62+
const output = `Decrypted APDU: ${plaintext}`;
63+
if (options.outfile) {
64+
fs.writeFileSync(options.outfile, output);
65+
} else {
66+
console.log(output);
67+
}
68+
} catch (err) {
69+
console.error('Decryption failed:', err.message);
70+
}
71+
});
72+
73+
program
74+
.command('auth <system_title> <frame_counter> <encryption_key> <authentication_key> <stoc>')
75+
.description('Generate an authentication tag for an APDU')
76+
.action((system_title, frame_counter, encryption_key, authentication_key, stoc) => {
77+
const tag = authAPDU(system_title, frame_counter, encryption_key, authentication_key, stoc);
78+
console.log(`Authentication TAG: ${tag}`);
79+
});
80+
81+
program.parse(process.argv);

js/index.js

Lines changed: 0 additions & 59 deletions
This file was deleted.

js/lib/dlmsCryptoTool.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
const crypto = require("crypto");
2+
3+
// Security header constans
4+
const SECURITY_HEADER = {
5+
6+
auth: '10',
7+
data: '30'
8+
9+
};
10+
11+
// Create IV by concatenating system_title and frame_counter (hex string)
12+
function createIV(system_title, frame_counter){
13+
14+
// Concatenation of hex strings and conversion to Buffer
15+
return Buffer.from(system_title + frame_counter, 'hex');
16+
}
17+
18+
/**
19+
* Encrypts an APDU message using AES-GCM.
20+
* @param {string} system_title - Hex string
21+
* @param {string} frame_counter - Hex string
22+
* @param {string} encryption_key - Hex string key
23+
* @param {string} additional_auth_data - Additional AAD without the header (hex string)
24+
* @param {string} plaintext - Plaintext APDU as a hex string
25+
* @returns {string} The ciphertext with appended auth tag as a hex string
26+
*/
27+
function encryptAPDU(system_title, frame_counter, encryption_key,additional_auth_data,
28+
plaintext) {
29+
30+
const iv = createIV(system_title, frame_counter);
31+
const keyBuffer = Buffer.from(encryption_key, 'hex');
32+
const aadBuffer = Buffer.from(SECURITY_HEADER.data + additional_auth_data, 'hex');
33+
34+
const cipher = crypto.createCipheriv('aes-128-gcm', keyBuffer, iv, {authTagLength: 16});
35+
cipher.setAAD(aadBuffer);
36+
37+
// Encrypt the plaintext (which is provided as a hex string)
38+
const plaitextBuffer = Buffer.from(plaintext, 'hex');
39+
const encrypted = Buffer.concat([cipher.update(plaitextBuffer), cipher.final()]);
40+
const tag = cipher.getAuthTag();
41+
42+
// Return concatenated ciphertext + tag as hex string.
43+
return Buffer.concat([encrypted, tag]).toString('hex');
44+
45+
}
46+
47+
/**
48+
* Decrypts an APDU message using AES-GCM.
49+
* @param {string} system_title - Hex string representing the system title.
50+
* @param {string} frame_counter - Hex string representing the frame counter.
51+
* @param {string} encryption_key - Hex string decryption key.
52+
* @param {string} additional_auth_data - Hex string of additional authentication data.
53+
* @param {string} ciphertextWithTagHex - Hex string that contains both ciphertext and appended auth tag.
54+
* @returns {string} The decrypted APDU as a hex string.
55+
*/
56+
function decryptAPDU(system_title, frame_counter, encryption_key,
57+
additional_auth_data, ciphertextWithTagHex) {
58+
59+
const buffer = Buffer.from(ciphertextWithTagHex, 'hex');
60+
const iv = createIV(system_title, frame_counter);
61+
const keybuffer = Buffer.from(encryption_key, 'hex');
62+
const aadBuffer = Buffer.from(SECURITY_HEADER.data + additional_auth_data, 'hex');
63+
64+
// Separate tag (last 16 bytes) from cipheredtext.
65+
const tag = buffer.slice(buffer.length - 16);
66+
const cipheredtext = buffer.slice(0, buffer.length - 16);
67+
68+
const decipher = crypto.createDecipheriv('aes-128-gcm', keybuffer, iv, {authTagLength: 16});
69+
70+
decipher.setAAD(aadBuffer);
71+
decipher.setAuthTag(tag);
72+
73+
const decrypted = Buffer.concat([decipher.update(cipheredtext), decipher.final()]);
74+
75+
return decrypted.toString('hex');
76+
77+
}
78+
79+
/**
80+
* Generates an authentication tag by encrypting an empty plaintext.
81+
* @param {string} system_title - Hex string representing the system title.
82+
* @param {string} frame_counter - Hex string representing the frame counter.
83+
* @param {string} encryption_key - Hex string encryption key.
84+
* @param {string} authentication_key - Hex string authentication key.
85+
* @param {string} stoc - Hex string
86+
* @returns {string} A truncated auth tag (hex string, last 8 hex characters removed).
87+
*/
88+
function authAPDU(system_title, frame_counter, encryption_key,authentication_key,stoc){
89+
90+
const iv = createIV(system_title, frame_counter);
91+
const keybuffer = Buffer.from(encryption_key, 'hex');
92+
const aadBuffer = Buffer.from(SECURITY_HEADER.auth + authentication_key + stoc, 'hex');
93+
94+
const cipher = crypto.createCipheriv('aes-128-gcm', keybuffer, iv, {authTagLength: 16});
95+
96+
cipher.setAAD(aadBuffer);
97+
98+
// Encrypt an empty plaintext buffer
99+
cipher.update(Buffer.alloc(0));
100+
cipher.final();
101+
102+
const tag = cipher.getAuthTag().toString('hex');
103+
104+
// Truncate last 8 hex characters
105+
return tag.slice(0, tag.length - 8);
106+
107+
}
108+
109+
module.exports = {
110+
createIV,
111+
encryptAPDU,
112+
decryptAPDU,
113+
authAPDU,
114+
SECURITY_HEADER,
115+
};

0 commit comments

Comments
 (0)