package com.desmachine import android.util.Base64 import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableMap import javax.crypto.Cipher import javax.crypto.SecretKeyFactory import javax.crypto.spec.DESKeySpec import javax.crypto.spec.IvParameterSpec class DesMachineModule(reactContext: ReactApplicationContext) : NativeDesMachineSpec(reactContext) { /** * Encrypt the provided plaintext using DES algorithm. * Stateless operation - all parameters are passed with each call. * * @param params ReadableMap containing: * - key: String (required, minimum 8 characters) * - mode: String (optional, default: ECB) - ECB, CBC, CFB, OFB, CTR * - padding: String (optional, default: PKCS7) - PKCS7, ISO10126, ZERO, NONE * - outputFormat: String (optional, default: BASE64) - BASE64, HEX * @param text The plaintext to encrypt * @return Encrypted string in the configured output format (BASE64 or HEX) */ override fun encrypt(params: ReadableMap, text: String): String { val config = parseParams(params) val cipher = createCipher(Cipher.ENCRYPT_MODE, config) val inputBytes = applyPaddingIfNeeded(text.toByteArray(Charsets.UTF_8), config.padding) val encryptedBytes = cipher.doFinal(inputBytes) return formatOutput(encryptedBytes, config.outputFormat) } /** * Decrypt the provided ciphertext using DES algorithm. * Stateless operation - all parameters are passed with each call. * * @param params ReadableMap containing: * - key: String (required, minimum 8 characters) * - mode: String (optional, default: ECB) - ECB, CBC, CFB, OFB, CTR * - padding: String (optional, default: PKCS7) - PKCS7, ISO10126, ZERO, NONE * - outputFormat: String (optional, default: BASE64) - BASE64, HEX * @param text Encrypted string in the configured format (BASE64 or HEX) * @return Decrypted plaintext */ override fun decrypt(params: ReadableMap, text: String): String { val config = parseParams(params) val cipher = createCipher(Cipher.DECRYPT_MODE, config) val encryptedBytes = parseInput(text, config.outputFormat) val decryptedBytes = cipher.doFinal(encryptedBytes) return String(removePaddingIfNeeded(decryptedBytes, config.padding), Charsets.UTF_8) } /** * Parse and validate parameters from ReadableMap. */ private fun parseParams(params: ReadableMap): DesConfig { val key = params.getString("key") ?: throw IllegalArgumentException("Key is required") require(key.length >= 8) { "DES key must be at least 8 characters long" } val iv = params.getString("iv") if (iv != null) { require(iv.length == 8) { "IV must be exactly 8 characters long" } } return DesConfig( key = key, iv = iv, mode = params.getString("mode") ?: MODE_ECB, padding = params.getString("padding") ?: PADDING_PKCS7, outputFormat = params.getString("outputFormat") ?: FORMAT_BASE64 ) } /** * Create and initialize a cipher with the given configuration. */ private fun createCipher(opMode: Int, config: DesConfig): Cipher { val keyBytes = config.key.toByteArray(Charsets.UTF_8) val desKeySpec = DESKeySpec(keyBytes) val keyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM) val secretKey = keyFactory.generateSecret(desKeySpec) val transformation = buildTransformation(config.mode, config.padding) val cipher = Cipher.getInstance(transformation) if (config.mode == MODE_ECB) { cipher.init(opMode, secretKey) } else { // Use provided IV or first 8 bytes of key as IV (DES block size is 8 bytes) val ivBytes = config.iv?.toByteArray(Charsets.UTF_8) ?: keyBytes.copyOf(8) val ivSpec = IvParameterSpec(ivBytes) cipher.init(opMode, secretKey, ivSpec) } return cipher } /** * Build the cipher transformation string based on mode and padding. */ private fun buildTransformation(mode: String, padding: String): String { val javaPadding = when (padding) { PADDING_PKCS7 -> "PKCS5Padding" // PKCS5 is equivalent to PKCS7 for DES PADDING_ISO10126 -> "ISO10126Padding" PADDING_ZERO, PADDING_NONE -> "NoPadding" else -> "PKCS5Padding" } return "$DES_ALGORITHM/$mode/$javaPadding" } /** * Apply manual padding for ZERO padding mode. */ private fun applyPaddingIfNeeded(data: ByteArray, padding: String): ByteArray { if (padding != PADDING_ZERO && padding != PADDING_NONE) { return data } val blockSize = 8 // DES block size val paddingLength = blockSize - (data.size % blockSize) if (paddingLength == blockSize && padding == PADDING_NONE) { return data } val paddedData = ByteArray(data.size + paddingLength) System.arraycopy(data, 0, paddedData, 0, data.size) // Zero padding: remaining bytes are already 0 return paddedData } /** * Remove manual padding for ZERO padding mode. */ private fun removePaddingIfNeeded(data: ByteArray, padding: String): ByteArray { if (padding != PADDING_ZERO) { return data } // Find last non-zero byte var lastIndex = data.size - 1 while (lastIndex >= 0 && data[lastIndex] == 0.toByte()) { lastIndex-- } return data.copyOf(lastIndex + 1) } /** * Format encrypted bytes to the configured output format. */ private fun formatOutput(bytes: ByteArray, outputFormat: String): String { return when (outputFormat) { FORMAT_HEX -> bytes.joinToString("") { "%02x".format(it) } else -> Base64.encodeToString(bytes, Base64.NO_WRAP) } } /** * Parse input string from the configured format to bytes. */ private fun parseInput(text: String, outputFormat: String): ByteArray { return when (outputFormat) { FORMAT_HEX -> text.chunked(2).map { it.toInt(16).toByte() }.toByteArray() else -> Base64.decode(text, Base64.NO_WRAP) } } /** * Data class to hold DES configuration. */ private data class DesConfig( val key: String, val iv: String?, val mode: String, val padding: String, val outputFormat: String ) companion object { const val NAME = NativeDesMachineSpec.NAME private const val DES_ALGORITHM = "DES" // Mode constants private const val MODE_ECB = "ECB" // Padding constants private const val PADDING_PKCS7 = "PKCS7" private const val PADDING_ISO10126 = "ISO10126" private const val PADDING_ZERO = "ZERO" private const val PADDING_NONE = "NONE" // Output format constants private const val FORMAT_BASE64 = "BASE64" private const val FORMAT_HEX = "HEX" } }