diff --git a/index.html b/index.html
index 1b6898f..81e9048 100644
--- a/index.html
+++ b/index.html
@@ -3,19 +3,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1115,7 +1256,236 @@ function genConvert() {
document.getElementById('conv-result').value = result;
}
+// ===== Secured Packet UI =====
+function updateSp() {
+ document.getElementById('sp-spi1-hex').value = document.getElementById('sp-spi1').value;
+ const spi2Base = parseInt(document.getElementById('sp-spi2').value, 16);
+ const sm = parseInt(document.getElementById('sp-spi2-sm').value, 16);
+ const spi2Byte = spi2Base | (sm << 2);
+ document.getElementById('sp-spi2-hex').value = spi2Byte.toString(16).padStart(2, '0').toUpperCase();
+}
+function updateSpKic() {
+ const idx = parseInt(document.getElementById('sp-kic-idx').value, 16);
+ const alg = parseInt(document.getElementById('sp-kic-alg').value, 16);
+ const byte = (idx << 4) | alg;
+ document.getElementById('sp-kic-hex').value = byte.toString(16).padStart(2, '0').toUpperCase();
+}
+function updateSpKid() {
+ const idx = parseInt(document.getElementById('sp-kid-idx').value, 16);
+ const alg = parseInt(document.getElementById('sp-kid-alg').value, 16);
+ const byte = (idx << 4) | alg;
+ document.getElementById('sp-kid-hex').value = byte.toString(16).padStart(2, '0').toUpperCase();
+}
+
+// ===== DES / 3DES using des.js library (bundled as des-bundle.js) =====
+function hexToBytes(s) {
+ const len = s.length / 2;
+ const b = new Uint8Array(len);
+ for (let i = 0; i < len; i++) b[i] = parseInt(s.substr(i * 2, 2), 16);
+ return b;
+}
+
+function bytesToHex(b) {
+ return Array.from(b).map(x => x.toString(16).padStart(2, '0').toUpperCase()).join('');
+}
+
+function des3Keys(keyBytes) {
+ const kLen = keyBytes.length;
+ if (kLen === 8) return [keyBytes, keyBytes, keyBytes];
+ if (kLen === 16) return [keyBytes.subarray(0,8), keyBytes.subarray(8,16), keyBytes.subarray(0,8)];
+ if (kLen === 24) return [keyBytes.subarray(0,8), keyBytes.subarray(8,16), keyBytes.subarray(16,24)];
+ throw new Error('Invalid key length: ' + kLen + ' bytes (expected 8, 16, or 24)');
+}
+
+function des3EncryptBlock(block, keyBytes) {
+ const [k1, k2, k3] = des3Keys(keyBytes);
+ const c1 = des.DES.create({type: 'encrypt', key: k1});
+ const c2 = des.DES.create({type: 'decrypt', key: k2});
+ const c3 = des.DES.create({type: 'encrypt', key: k3});
+ const out = new Uint8Array(8);
+ c1._update(block, 0, out, 0);
+ c2._update(out, 0, out, 0);
+ c3._update(out, 0, out, 0);
+ return out;
+}
+
+function des3DecryptBlock(block, keyBytes) {
+ const [k1, k2, k3] = des3Keys(keyBytes);
+ const c1 = des.DES.create({type: 'decrypt', key: k3});
+ const c2 = des.DES.create({type: 'encrypt', key: k2});
+ const c3 = des.DES.create({type: 'decrypt', key: k1});
+ const out = new Uint8Array(8);
+ c1._update(block, 0, out, 0);
+ c2._update(out, 0, out, 0);
+ c3._update(out, 0, out, 0);
+ return out;
+}
+
+function desEncryptBlock(block, key) {
+ const c = des.DES.create({type: 'encrypt', key: key});
+ const out = new Uint8Array(8);
+ c._update(block, 0, out, 0);
+ return out;
+}
+
+function desDecryptBlock(block, key) {
+ const c = des.DES.create({type: 'decrypt', key: key});
+ const out = new Uint8Array(8);
+ c._update(block, 0, out, 0);
+ return out;
+}
+
+function des3CbcEncrypt(data, keyBytes, iv) {
+ const bs = 8;
+ const padded = zeroPad(data, bs);
+ const out = new Uint8Array(padded.length);
+ let prev = iv;
+ for (let i = 0; i < padded.length; i += bs) {
+ const block = padded.subarray(i, i + bs);
+ const xored = new Uint8Array(bs);
+ for (let j = 0; j < bs; j++) xored[j] = block[j] ^ prev[j];
+ const enc = des3EncryptBlock(xored, keyBytes);
+ out.set(enc, i);
+ prev = enc;
+ }
+ return out;
+}
+
+function xorBytes(a, b) {
+ const len = Math.min(a.length, b.length);
+ const out = new Uint8Array(len);
+ for (let i = 0; i < len; i++) out[i] = a[i] ^ b[i];
+ return out;
+}
+
+function zeroPad(data, blockSize) {
+ const padLen = blockSize - (data.length % blockSize);
+ if (padLen === blockSize) return data;
+ const out = new Uint8Array(data.length + padLen);
+ out.set(data);
+ for (let i = data.length; i < out.length; i++) out[i] = 0x00;
+ return out;
+}
+
+function retailMac(data, keyBytes) {
+ const bs = 8;
+ const padded = zeroPad(data, bs);
+ const [k1, k2, k3] = des3Keys(keyBytes);
+ let prev = new Uint8Array(bs);
+ for (let i = 0; i < padded.length; i += bs) {
+ const block = padded.subarray(i, i + bs);
+ const xored = xorBytes(block, prev);
+ let enc;
+ if (keyBytes.length === 8) {
+ enc = desEncryptBlock(xored, k1);
+ } else {
+ enc = des3EncryptBlock(xored, keyBytes);
+ }
+ if (i + bs === padded.length && keyBytes.length >= 16) {
+ enc = desDecryptBlock(enc, k2);
+ enc = desEncryptBlock(enc, k1);
+ }
+ prev = enc;
+ }
+ return prev;
+}
+
+// ===== Secure Packet assembly =====
+function genSp() {
+ const apduHex = (document.getElementById('sp-apdu').value || '').replace(/[^0-9a-fA-F]/g, '');
+ if (!apduHex) { document.getElementById('sp-result').value = 'Ошибка: укажите APDU'; return; }
+
+ const spi1 = parseInt(document.getElementById('sp-spi1').value, 16);
+ const spi2 = parseInt(document.getElementById('sp-spi2-hex').value, 16);
+ const kicHex = document.getElementById('sp-kic-hex').value;
+ const kidHex = document.getElementById('sp-kid-hex').value;
+ const tarHex = (document.getElementById('sp-tar').value || '').replace(/[^0-9a-fA-F]/g, '').padEnd(6, '0').slice(0, 6);
+ const cntrHex = (document.getElementById('sp-cntr').value || '').replace(/[^0-9a-fA-F]/g, '').padEnd(10, '0').slice(0, 10);
+ const kicKeyHex = (document.getElementById('sp-kic-key').value || '').replace(/[^0-9a-fA-F]/g, '');
+ const kidKeyHex = (document.getElementById('sp-kid-key').value || '').replace(/[^0-9a-fA-F]/g, '');
+ const padByte = parseInt(document.getElementById('sp-padding').value, 16);
+
+ const ciphering = (spi1 & 0x04) !== 0;
+ const hasMac = (spi1 & 0x03) === 0x02;
+
+ const resultEl = document.getElementById('sp-result');
+
+ let kicKey, kidKey;
+ try {
+ if (kicKeyHex) {
+ kicKey = hexToBytes(kicKeyHex);
+ if (kicKey.length !== 8 && kicKey.length !== 16 && kicKey.length !== 24)
+ { resultEl.value = 'Ошибка: KIc key must be 8, 16, or 24 bytes'; return; }
+ }
+ } catch (e) { resultEl.value = 'Ошибка KIc key: ' + e.message; return; }
+
+ try {
+ if (kidKeyHex) {
+ kidKey = hexToBytes(kidKeyHex);
+ if (kidKey.length !== 8 && kidKey.length !== 16 && kidKey.length !== 24)
+ { resultEl.value = 'Ошибка: KID key must be 8, 16, or 24 bytes'; return; }
+ }
+ } catch (e) { resultEl.value = 'Ошибка KID key: ' + e.message; return; }
+
+ if (ciphering && !kicKey) { resultEl.value = 'Ошибка: ciphering requires KIc key'; return; }
+ if (hasMac && !kidKey) { resultEl.value = 'Ошибка: MAC requires KID key'; return; }
+
+ const apdu = hexToBytes(apduHex);
+
+ const pCntr = (8 - (apdu.length % 8)) % 8;
+ const pCntrByte = pCntr;
+
+ const macLen = hasMac ? 8 : 0;
+
+ const spiOff = 4;
+ const kicOff = 6;
+ const secDataOff = 17 + macLen;
+
+ const packetLen = secDataOff + apdu.length + pCntr;
+ const packet = new Uint8Array(packetLen);
+
+ packet[0] = 0x02;
+ packet[2] = 0x01;
+ packet[4] = spi1;
+ packet[5] = spi2;
+ packet[6] = parseInt(kicHex, 16);
+ packet[7] = parseInt(kidHex, 16);
+ packet[8] = parseInt(tarHex.substr(0,2), 16);
+ packet[9] = parseInt(tarHex.substr(2,2), 16);
+ packet[10] = parseInt(tarHex.substr(4,2), 16);
+ packet[11] = parseInt(cntrHex.substr(0,2), 16);
+ packet[12] = parseInt(cntrHex.substr(2,2), 16);
+ packet[13] = parseInt(cntrHex.substr(4,2), 16);
+ packet[14] = parseInt(cntrHex.substr(6,2), 16);
+ packet[15] = parseInt(cntrHex.substr(8,2), 16);
+ packet[16] = pCntrByte;
+ for (let i = 0; i < macLen; i++) packet[17 + i] = 0x00;
+ packet.set(apdu, secDataOff);
+ for (let i = 0; i < pCntr; i++) packet[secDataOff + apdu.length + i] = padByte;
+
+ const chl = packetLen - spiOff;
+ packet[1] = packetLen - 2;
+ packet[3] = chl;
+
+ if (hasMac && kidKey) {
+ const mac = retailMac(packet, kidKey);
+ packet.set(mac, 17);
+ }
+
+ if (ciphering && kicKey) {
+ const cipherLen = packetLen - kicOff;
+ const toEncrypt = packet.subarray(kicOff, packetLen);
+ const encrypted = des3CbcEncrypt(toEncrypt, kicKey, new Uint8Array(8));
+ packet.set(encrypted, kicOff);
+ }
+
+ resultEl.value = bytesToHex(packet);
+}
+
// Init
+updateSp();
+updateSpKic();
+updateSpKid();
updateSimUsimFields('sim');
updateSimSelectMethod('sim');
updateSimSelectSection('sim');