init.lua (21493B)
1 -- Crypto CLI Application 2 -- Supports: hashing, signing, key exchange, key derivation, random generation 3 4 -- Base64 encoding/decoding implementation 5 local base64 = {} 6 7 -- Base64 encoding table 8 local b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' 9 10 -- Base64 encode 11 function base64.encode(data) 12 if not data or #data == 0 then return "" end 13 14 local result = {} 15 local padding = (3 - #data % 3) % 3 16 17 -- Process 3 bytes at a time 18 for i = 1, #data, 3 do 19 local b1 = string.byte(data, i) 20 local b2 = string.byte(data, i + 1) or 0 21 local b3 = string.byte(data, i + 2) or 0 22 23 local n = b1 * 65536 + b2 * 256 + b3 24 25 local c1 = math.floor(n / 262144) % 64 + 1 26 local c2 = math.floor(n / 4096) % 64 + 1 27 local c3 = math.floor(n / 64) % 64 + 1 28 local c4 = n % 64 + 1 29 30 table.insert(result, b64chars:sub(c1, c1)) 31 table.insert(result, b64chars:sub(c2, c2)) 32 33 if i + 1 <= #data then 34 table.insert(result, b64chars:sub(c3, c3)) 35 else 36 table.insert(result, '=') 37 end 38 39 if i + 2 <= #data then 40 table.insert(result, b64chars:sub(c4, c4)) 41 else 42 table.insert(result, '=') 43 end 44 end 45 46 return table.concat(result) 47 end 48 49 -- Base64 decode 50 function base64.decode(data) 51 if not data then return nil end 52 53 -- Remove whitespace 54 data = data:gsub("%s+", "") 55 56 if #data == 0 then return "" end 57 if #data % 4 ~= 0 then return nil, "Invalid base64 string length" end 58 59 -- Build decode table 60 local decode_table = {} 61 for i = 1, 64 do 62 decode_table[b64chars:sub(i, i)] = i - 1 63 end 64 decode_table['='] = 0 65 66 local result = {} 67 68 -- Process 4 characters at a time 69 for i = 1, #data, 4 do 70 local c1 = decode_table[data:sub(i, i)] 71 local c2 = decode_table[data:sub(i + 1, i + 1)] 72 local c3 = decode_table[data:sub(i + 2, i + 2)] 73 local c4 = decode_table[data:sub(i + 3, i + 3)] 74 75 if not c1 or not c2 or not c3 or not c4 then 76 return nil, "Invalid base64 character" 77 end 78 79 local n = c1 * 262144 + c2 * 4096 + c3 * 64 + c4 80 81 local b1 = math.floor(n / 65536) % 256 82 local b2 = math.floor(n / 256) % 256 83 local b3 = n % 256 84 85 table.insert(result, string.char(b1)) 86 87 if data:sub(i + 2, i + 2) ~= '=' then 88 table.insert(result, string.char(b2)) 89 end 90 91 if data:sub(i + 3, i + 3) ~= '=' then 92 table.insert(result, string.char(b3)) 93 end 94 end 95 96 return table.concat(result) 97 end 98 99 -- Helper function to convert binary data to hex string 100 local function toHex(data) 101 if not data then return "nil" end 102 local hex = "" 103 for i = 1, #data do 104 hex = hex .. string.format("%02x", string.byte(data, i)) 105 end 106 return hex 107 end 108 109 -- Helper function to convert hex string to binary data 110 local function fromHex(hex) 111 if not hex then return nil end 112 hex = hex:gsub("%s+", "") -- Remove whitespace 113 if #hex % 2 ~= 0 then 114 return nil, "Hex string must have even length" 115 end 116 local binary = "" 117 for i = 1, #hex, 2 do 118 local byte = tonumber(hex:sub(i, i+1), 16) 119 if not byte then 120 return nil, "Invalid hex character at position " .. i 121 end 122 binary = binary .. string.char(byte) 123 end 124 return binary 125 end 126 127 -- Helper function to detect and convert key from hex or base64 to binary 128 local function parseKey(keyStr) 129 if not keyStr then return nil, "No key provided" end 130 131 -- Check if it looks like hex (only hex chars, even length) 132 local hexClean = keyStr:gsub("%s+", "") 133 if hexClean:match("^[0-9a-fA-F]+$") and #hexClean % 2 == 0 then 134 -- It's hex 135 return fromHex(hexClean) 136 end 137 138 -- Check if it looks like base64 139 local b64Clean = keyStr:gsub("%s+", "") 140 if b64Clean:match("^[A-Za-z0-9+/]+=*$") and #b64Clean % 4 == 0 then 141 -- It's base64 142 local decoded = base64.decode(b64Clean) 143 if decoded then 144 return decoded 145 end 146 end 147 148 -- Assume it's raw binary/text 149 return keyStr 150 end 151 152 -- Helper function to format output based on options 153 local function formatOutput(data, options) 154 if not data then return "nil" end 155 156 if options.base64 then 157 return base64.encode(data) 158 else 159 -- Default to hex 160 return toHex(data) 161 end 162 end 163 164 -- Parse command-line arguments (args is a table) 165 local function parseArgs(args) 166 local options = {} 167 local positional = {} 168 local i = 1 169 170 while i <= #args do 171 local arg = args[i] 172 173 if arg:sub(1, 1) == "-" then 174 -- Option flag 175 if arg == "-key" or arg == "-k" then 176 i = i + 1 177 if i <= #args then 178 options.key = args[i] 179 else 180 return nil, "Option " .. arg .. " requires a value" 181 end 182 elseif arg == "-salt" or arg == "-s" then 183 i = i + 1 184 if i <= #args then 185 options.salt = args[i] 186 else 187 return nil, "Option " .. arg .. " requires a value" 188 end 189 elseif arg == "-iterations" or arg == "-i" then 190 i = i + 1 191 if i <= #args then 192 options.iterations = tonumber(args[i]) 193 if not options.iterations then 194 return nil, "Iterations must be a number" 195 end 196 else 197 return nil, "Option " .. arg .. " requires a value" 198 end 199 elseif arg == "-hex" then 200 options.hex = true 201 elseif arg == "-b64" or arg == "-base64" then 202 options.base64 = true 203 else 204 return nil, "Unknown option: " .. arg 205 end 206 else 207 -- Positional argument 208 table.insert(positional, arg) 209 end 210 211 i = i + 1 212 end 213 214 return positional, options 215 end 216 217 -- Check if crypto library is available 218 if not crypto then 219 print("Error: crypto library not available") 220 return 221 end 222 223 -- Get command-line arguments from the sandbox environment 224 -- The args table is provided by the run module after parsing _G.args.argStr 225 local args = args or {} 226 227 if #args == 0 then 228 print("LuajitOS Crypto Utility v1.0.0") 229 print("") 230 print("Usage: crypto <command> [options] <data>") 231 print("") 232 print("Hash Commands:") 233 print(" MD5 <data> - MD5 hash") 234 print(" SHA1 <data> - SHA-1 hash") 235 print(" SHA256 <data> - SHA-256 hash") 236 print(" SHA512 <data> - SHA-512 hash") 237 print(" SHA3-256 <data> - SHA3-256 hash") 238 print(" SHA3-512 <data> - SHA3-512 hash") 239 print(" BLAKE2b-256 <data> - BLAKE2b-256 hash") 240 print(" BLAKE2b-512 <data> - BLAKE2b-512 hash") 241 print(" CRC32 <data> - CRC32 checksum") 242 print("") 243 print("Key Derivation:") 244 print(" PBKDF2 <password> -salt <salt> [-i <iter>]") 245 print(" Argon2id <password> -salt <salt>") 246 print(" generateSalt - Generate random salt") 247 print("") 248 print("Digital Signatures (Ed25519):") 249 print(" Ed25519.keypair - Generate keypair") 250 print(" Ed25519.sign <data> -key <privkey_hex>") 251 print(" Ed25519.verify <data> <sig_hex> -key <pubkey_hex>") 252 print("") 253 print("Key Exchange (X25519):") 254 print(" X25519.keypair - Generate keypair") 255 print(" X25519.publicKey <privkey_hex> - Derive public key") 256 print(" X25519.shared <privkey_hex> <pubkey_hex> - Compute shared secret") 257 print("") 258 print("Encryption (key auto-detected as hex, base64, or raw):") 259 print(" encrypt <cipher> <key> <data>") 260 print(" decrypt <cipher> <key> <ciphertext>") 261 print(" Ciphers: AES-256-GCM, ChaCha20-Poly1305, Serpent-256-GCM,") 262 print(" Twofish-256-GCM, Salsa20-Poly1305") 263 print("") 264 print("Random:") 265 print(" random <bytes> - Generate random bytes") 266 print(" newKey <bytes> - Generate cryptographic key") 267 print("") 268 print("Options:") 269 print(" -key/-k <value> - Specify key (hex, base64, or raw)") 270 print(" -salt/-s <value> - Specify salt") 271 print(" -iterations/-i <num> - Number of iterations (default: 100000)") 272 print(" -b64/-base64 - Output in base64 format (default: hex)") 273 print("") 274 print("Examples:") 275 print(" crypto MD5 \"hello world\"") 276 print(" crypto SHA256 \"test123\" -b64") 277 print(" crypto PBKDF2 \"mypassword\" -salt \"randomsalt\" -i 100000") 278 print(" crypto Ed25519.keypair -b64") 279 print(" crypto random 32 -b64") 280 print(" crypto encrypt AES-256-GCM <key-hex-or-b64> \"secret data\"") 281 print(" crypto decrypt AES-256-GCM <key-hex-or-b64> <ciphertext>") 282 return 283 end 284 285 -- Parse arguments 286 local positional, options = parseArgs(args) 287 288 if not positional then 289 print("Error: " .. (options or "Failed to parse arguments")) 290 return 291 end 292 293 if #positional == 0 then 294 print("Error: No command specified. Use 'crypto' without arguments for help.") 295 return 296 end 297 298 -- Normalize command - uppercase for hash/kdf commands, preserve case for others 299 local rawCommand = positional[1] 300 local command = rawCommand:upper() 301 302 -- Map uppercase to proper mixed-case for specific commands 303 local commandMap = { 304 ["ED25519.KEYPAIR"] = "Ed25519.keypair", 305 ["ED25519.SIGN"] = "Ed25519.sign", 306 ["ED25519.VERIFY"] = "Ed25519.verify", 307 ["X25519.KEYPAIR"] = "X25519.keypair", 308 ["X25519.PUBLICKEY"] = "X25519.publicKey", 309 ["X25519.SHARED"] = "X25519.shared", 310 ["GENERATESALT"] = "generateSalt", 311 ["NEWKEY"] = "newKey", 312 ["ENCRYPT"] = "encrypt", 313 ["DECRYPT"] = "decrypt", 314 ["RANDOM"] = "random", 315 } 316 if commandMap[command] then 317 command = commandMap[command] 318 end 319 320 -- Hash commands 321 if command == "MD5" then 322 if #positional < 2 then 323 print("Error: MD5 requires data argument") 324 return 325 end 326 local data = positional[2] 327 local hash = crypto.MD5(data) 328 print(formatOutput(hash, options)) 329 330 elseif command == "SHA1" then 331 if #positional < 2 then 332 print("Error: SHA1 requires data argument") 333 return 334 end 335 local data = positional[2] 336 local hash = crypto.SHA1(data) 337 print(formatOutput(hash, options)) 338 339 elseif command == "SHA256" then 340 if #positional < 2 then 341 print("Error: SHA256 requires data argument") 342 return 343 end 344 local data = positional[2] 345 local hash = crypto.SHA256(data) 346 print(formatOutput(hash, options)) 347 348 elseif command == "SHA512" then 349 if #positional < 2 then 350 print("Error: SHA512 requires data argument") 351 return 352 end 353 local data = positional[2] 354 local hash = crypto.SHA512(data) 355 print(formatOutput(hash, options)) 356 357 elseif command == "SHA3-256" then 358 if #positional < 2 then 359 print("Error: SHA3-256 requires data argument") 360 return 361 end 362 local data = positional[2] 363 local hash = crypto["SHA3-256"](data) 364 print(formatOutput(hash, options)) 365 366 elseif command == "SHA3-512" then 367 if #positional < 2 then 368 print("Error: SHA3-512 requires data argument") 369 return 370 end 371 local data = positional[2] 372 local hash = crypto["SHA3-512"](data) 373 print(formatOutput(hash, options)) 374 375 elseif command == "BLAKE2b-256" then 376 if #positional < 2 then 377 print("Error: BLAKE2b-256 requires data argument") 378 return 379 end 380 local data = positional[2] 381 local hash = crypto["BLAKE2b-256"](data) 382 print(formatOutput(hash, options)) 383 384 elseif command == "BLAKE2b-512" then 385 if #positional < 2 then 386 print("Error: BLAKE2b-512 requires data argument") 387 return 388 end 389 local data = positional[2] 390 local hash = crypto["BLAKE2b-512"](data) 391 print(formatOutput(hash, options)) 392 393 elseif command == "CRC32" then 394 if #positional < 2 then 395 print("Error: CRC32 requires data argument") 396 return 397 end 398 local data = positional[2] 399 local hash = crypto.CRC32(data) 400 print(formatOutput(hash, options)) 401 402 -- Key Derivation 403 elseif command == "PBKDF2" then 404 if #positional < 2 then 405 print("Error: PBKDF2 requires password argument") 406 return 407 end 408 local password = positional[2] 409 local salt = options.salt 410 if not salt then 411 print("Error: PBKDF2 requires -salt option") 412 return 413 end 414 local iterations = options.iterations or 100000 415 local key = crypto.PBKDF2(password, salt, iterations, 32) 416 if key then 417 print(formatOutput(key, options)) 418 else 419 print("Error: PBKDF2 failed") 420 end 421 422 elseif command == "Argon2id" then 423 if #positional < 2 then 424 print("Error: Argon2id requires password argument") 425 return 426 end 427 local password = positional[2] 428 local salt = options.salt 429 if not salt then 430 print("Error: Argon2id requires -salt option") 431 return 432 end 433 local key = crypto.Argon2id(password, salt) 434 if key then 435 print(formatOutput(key, options)) 436 else 437 print("Error: Argon2id failed") 438 end 439 440 elseif command == "generateSalt" then 441 local salt = crypto.generateSalt() 442 if salt then 443 print(formatOutput(salt, options)) 444 else 445 print("Error: Failed to generate salt") 446 end 447 448 -- Ed25519 Digital Signatures 449 elseif command == "Ed25519.keypair" then 450 local pubkey, privkey = crypto.sign.Ed25519.keypair() 451 if pubkey and privkey then 452 print("Public Key: " .. formatOutput(pubkey, options)) 453 print("Private Key: " .. formatOutput(privkey, options)) 454 else 455 print("Error: Failed to generate Ed25519 keypair") 456 end 457 458 elseif command == "Ed25519.sign" then 459 if #positional < 2 then 460 print("Error: Ed25519.sign requires data argument") 461 return 462 end 463 local data = positional[2] 464 local privkey_hex = options.key 465 if not privkey_hex then 466 print("Error: Ed25519.sign requires -key option with private key (hex)") 467 return 468 end 469 local privkey, err = fromHex(privkey_hex) 470 if not privkey then 471 print("Error: Invalid private key hex: " .. err) 472 return 473 end 474 local signature = crypto.sign.Ed25519.sign(data, privkey) 475 if signature then 476 print(formatOutput(signature, options)) 477 else 478 print("Error: Failed to sign data") 479 end 480 481 elseif command == "Ed25519.verify" then 482 if #positional < 3 then 483 print("Error: Ed25519.verify requires data and signature arguments") 484 return 485 end 486 local data = positional[2] 487 local sig_hex = positional[3] 488 local pubkey_hex = options.key 489 if not pubkey_hex then 490 print("Error: Ed25519.verify requires -key option with public key (hex)") 491 return 492 end 493 local signature, err1 = fromHex(sig_hex) 494 if not signature then 495 print("Error: Invalid signature hex: " .. err1) 496 return 497 end 498 local pubkey, err2 = fromHex(pubkey_hex) 499 if not pubkey then 500 print("Error: Invalid public key hex: " .. err2) 501 return 502 end 503 local valid = crypto.sign.Ed25519.verify(data, signature, pubkey) 504 if valid then 505 print("Signature is VALID") 506 else 507 print("Signature is INVALID") 508 end 509 510 -- X25519 Key Exchange 511 elseif command == "X25519.keypair" then 512 local pubkey, privkey = crypto.keyExchange.X25519.keypair() 513 if pubkey and privkey then 514 print("Public Key: " .. formatOutput(pubkey, options)) 515 print("Private Key: " .. formatOutput(privkey, options)) 516 else 517 print("Error: Failed to generate X25519 keypair") 518 end 519 520 elseif command == "X25519.publicKey" then 521 if #positional < 2 then 522 print("Error: X25519.publicKey requires private key (hex) argument") 523 return 524 end 525 local privkey_hex = positional[2] 526 local privkey, err = fromHex(privkey_hex) 527 if not privkey then 528 print("Error: Invalid private key hex: " .. err) 529 return 530 end 531 local pubkey = crypto.keyExchange.X25519.publicKey(privkey) 532 if pubkey then 533 print(formatOutput(pubkey, options)) 534 else 535 print("Error: Failed to derive public key") 536 end 537 538 elseif command == "X25519.shared" then 539 if #positional < 3 then 540 print("Error: X25519.shared requires private key and public key (hex) arguments") 541 return 542 end 543 local privkey_hex = positional[2] 544 local pubkey_hex = positional[3] 545 local privkey, err1 = fromHex(privkey_hex) 546 if not privkey then 547 print("Error: Invalid private key hex: " .. err1) 548 return 549 end 550 local pubkey, err2 = fromHex(pubkey_hex) 551 if not pubkey then 552 print("Error: Invalid public key hex: " .. err2) 553 return 554 end 555 local shared = crypto.keyExchange.X25519.sharedSecret(privkey, pubkey) 556 if shared then 557 print(formatOutput(shared, options)) 558 else 559 print("Error: Failed to compute shared secret") 560 end 561 562 -- Random Generation 563 elseif command == "random" then 564 if #positional < 2 then 565 print("Error: random requires number of bytes argument") 566 return 567 end 568 local num_bytes = tonumber(positional[2]) 569 if not num_bytes or num_bytes <= 0 then 570 print("Error: Number of bytes must be a positive integer") 571 return 572 end 573 local random_data = crypto.random(num_bytes) 574 if random_data then 575 print(formatOutput(random_data, options)) 576 else 577 print("Error: Failed to generate random data") 578 end 579 580 elseif command == "newKey" then 581 if #positional < 2 then 582 print("Error: newKey requires number of bytes argument") 583 return 584 end 585 local num_bytes = tonumber(positional[2]) 586 if not num_bytes or num_bytes <= 0 then 587 print("Error: Number of bytes must be a positive integer") 588 return 589 end 590 local key = crypto.newKey(num_bytes) 591 if key then 592 print(formatOutput(key, options)) 593 else 594 print("Error: Failed to generate key") 595 end 596 597 -- Encryption 598 elseif command == "encrypt" then 599 if #positional < 4 then 600 print("Error: encrypt requires cipher, key, and data arguments") 601 print("Usage: crypto encrypt <cipher> <key> <data>") 602 return 603 end 604 local cipher = positional[2] 605 local keyStr = positional[3] 606 local plaintext = positional[4] 607 608 -- Auto-convert key from hex/base64 to binary 609 local key, err = parseKey(keyStr) 610 if not key then 611 print("Error: Invalid key: " .. (err or "unknown error")) 612 return 613 end 614 615 -- Map cipher name to crypto function 616 local cipherFuncs = { 617 ["AES-256-GCM"] = crypto["AES-256-GCM"], 618 ["ChaCha20-Poly1305"] = crypto["ChaCha20-Poly1305"], 619 ["Serpent-256-GCM"] = crypto["Serpent-256-GCM"], 620 ["Twofish-256-GCM"] = crypto["Twofish-256-GCM"], 621 ["Salsa20-Poly1305"] = crypto["Salsa20-Poly1305"] 622 } 623 624 local cipherMod = cipherFuncs[cipher] 625 if not cipherMod then 626 print("Error: Unknown cipher '" .. cipher .. "'") 627 print("Valid ciphers: AES-256-GCM, ChaCha20-Poly1305, Serpent-256-GCM, Twofish-256-GCM, Salsa20-Poly1305") 628 return 629 end 630 631 local ciphertext, encErr = cipherMod.encryptAsBase64(plaintext, key) 632 if ciphertext then 633 print(ciphertext) 634 else 635 print("Error: Encryption failed: " .. (encErr or "unknown error")) 636 end 637 638 -- Decryption 639 elseif command == "decrypt" then 640 if #positional < 4 then 641 print("Error: decrypt requires cipher, key, and ciphertext arguments") 642 print("Usage: crypto decrypt <cipher> <key> <ciphertext>") 643 return 644 end 645 local cipher = positional[2] 646 local keyStr = positional[3] 647 local ciphertext = positional[4] 648 649 -- Auto-convert key from hex/base64 to binary 650 local key, err = parseKey(keyStr) 651 if not key then 652 print("Error: Invalid key: " .. (err or "unknown error")) 653 return 654 end 655 656 -- Map cipher name to crypto function 657 local cipherFuncs = { 658 ["AES-256-GCM"] = crypto["AES-256-GCM"], 659 ["ChaCha20-Poly1305"] = crypto["ChaCha20-Poly1305"], 660 ["Serpent-256-GCM"] = crypto["Serpent-256-GCM"], 661 ["Twofish-256-GCM"] = crypto["Twofish-256-GCM"], 662 ["Salsa20-Poly1305"] = crypto["Salsa20-Poly1305"] 663 } 664 665 local cipherMod = cipherFuncs[cipher] 666 if not cipherMod then 667 print("Error: Unknown cipher '" .. cipher .. "'") 668 print("Valid ciphers: AES-256-GCM, ChaCha20-Poly1305, Serpent-256-GCM, Twofish-256-GCM, Salsa20-Poly1305") 669 return 670 end 671 672 local plaintext, decErr = cipherMod.decryptFromBase64(ciphertext, key) 673 if plaintext then 674 print(plaintext) 675 else 676 print("Error: Decryption failed: " .. (decErr or "unknown error")) 677 end 678 679 else 680 print("Error: Unknown command '" .. command .. "'") 681 print("Use 'crypto' without arguments for help") 682 end 683 684 -- Export crypto library for other apps to import 685 -- This allows other apps to use: local crypto = apps["com.luajitos.crypto"]:call("crypto") 686 if app and app.export then 687 app:export({ 688 name = "crypto", 689 func = function() 690 return crypto 691 end, 692 description = "Returns the global crypto library", 693 returns = "table" 694 }) 695 end