/* |-------------------------------------------------------------------------- | Manifest |-------------------------------------------------------------------------- | | Name: | - Cipher | | Purpose: | - Provide reusable AES-256 encryption and decryption utilities for | standard and salted file-based cryptographic workflows. | | Guideline: | - Use only AES-256 compatible keys. | - Use encrypt-standard for non-salted encryption workflows. | - Use encrypt-salted for self-contained encrypted file workflows. | - Use decrypt-standard for non-salted encrypted files. | - Use decrypt-salted for self-contained encrypted files. | - Always provide valid input and output file paths. | | Example: | - ObrimCipher("encrypt-standard", config) | - ObrimCipher("encrypt-salted", config) | - ObrimCipher("decrypt-standard", config) | - ObrimCipher("decrypt-salted", config) | |-------------------------------------------------------------------------- */ /* |-------------------------------------------------------------------------- | Credit |-------------------------------------------------------------------------- | | Contributor: | - Rajon Ahmed | - Scionite | |-------------------------------------------------------------------------- */ package cipher import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/json" "errors" "io" "os" "path/filepath" "go/module/essential/visible/service/helper/log" "go/module/essential/visible/service/helper/retriever" "go/module/essential/visible/service/helper/status" ) const ( obrimCipherAlgorithm = "AES-256" obrimCipherSignature = "OBRIMCIPHER" obrimCipherVersion = "1" obrimCipherSaltSize = 32 obrimCipherNonceSize = 12 ) type obrimCipherResponse struct { Status bool `json:"status"` Code string `json:"code"` Payload any `json:"payload"` } // ObrimCipher executes encryption and decryption workflows. func ObrimCipher(operationType string, config map[string]any) map[string]any { _, _ = retriever.ObrimRetriever, status.ObrimStatus log.ObrimLog("INIT", "Cipher operation initialized.") if err := obrimCipherValidateType(operationType); err != nil { return obrimCipherBuildResponse(false, "FAILED_INVALID_TYPE", nil) } if err := obrimCipherValidateConfig(config); err != nil { return obrimCipherBuildResponse(false, "FAILED_INVALID_CONFIG", nil) } keyValue := config["key"].(string) if err := obrimCipherValidateKey(keyValue); err != nil { return obrimCipherBuildResponse(false, "FAILED_INVALID_KEY", nil) } inputPath, err := obrimCipherResolvePath(config["input"].(string)) if err != nil { return obrimCipherBuildResponse(false, "FAILED_INPUT_PATH_VALIDATION", nil) } outputPath, err := obrimCipherResolvePath(config["output"].(string)) if err != nil { return obrimCipherBuildResponse(false, "FAILED_OUTPUT_PATH_VALIDATION", nil) } inputData, err := obrimCipherReadInput(inputPath) if err != nil { return obrimCipherBuildResponse(false, "FAILED_SOURCE_ACCESS", nil) } switch operationType { case "encrypt-standard": encryptedData, err := obrimCipherEncryptStandard([]byte(keyValue), inputData) if err != nil { return obrimCipherBuildResponse(false, "FAILED_ENCRYPTION", nil) } if err := obrimCipherWriteOutput(outputPath, encryptedData); err != nil { return obrimCipherBuildResponse(false, "FAILED_OUTPUT_WRITE", nil) } return obrimCipherBuildResponse(true, "SUCCESS_ENCRYPT_STANDARD", map[string]any{ "operation": operationType, "algorithm": obrimCipherAlgorithm, "inputPath": inputPath, "outputPath": outputPath, }) case "encrypt-salted": salt, err := obrimCipherGenerateSalt() if err != nil { return obrimCipherBuildResponse(false, "FAILED_SALT_GENERATION", nil) } encryptedData, err := obrimCipherEncryptSalted([]byte(keyValue), salt, inputData) if err != nil { return obrimCipherBuildResponse(false, "FAILED_ENCRYPTION", nil) } if err := obrimCipherWriteOutput(outputPath, encryptedData); err != nil { return obrimCipherBuildResponse(false, "FAILED_OUTPUT_WRITE", nil) } return obrimCipherBuildResponse(true, "SUCCESS_ENCRYPT_SALTED", map[string]any{ "operation": operationType, "algorithm": obrimCipherAlgorithm, "format": obrimCipherSignature, "version": obrimCipherVersion, "inputPath": inputPath, "outputPath": outputPath, }) case "decrypt-standard": decryptedData, err := obrimCipherDecryptStandard([]byte(keyValue), inputData) if err != nil { return obrimCipherBuildResponse(false, "FAILED_DECRYPTION", nil) } if err := obrimCipherWriteOutput(outputPath, decryptedData); err != nil { return obrimCipherBuildResponse(false, "FAILED_OUTPUT_WRITE", nil) } return obrimCipherBuildResponse(true, "SUCCESS_DECRYPT_STANDARD", map[string]any{ "operation": operationType, "algorithm": obrimCipherAlgorithm, "inputPath": inputPath, "outputPath": outputPath, }) case "decrypt-salted": signature, version, err := obrimCipherParseHeader(inputData) if err != nil { if err.Error() == "version" { return obrimCipherBuildResponse(false, "FAILED_VERSION_VALIDATION", nil) } return obrimCipherBuildResponse(false, "FAILED_SIGNATURE_VALIDATION", nil) } salt, err := obrimCipherExtractSalt(inputData) if err != nil { return obrimCipherBuildResponse(false, "FAILED_SALT_EXTRACTION", nil) } payload, err := obrimCipherExtractPayload(inputData) if err != nil { return obrimCipherBuildResponse(false, "FAILED_PAYLOAD_EXTRACTION", nil) } decryptedData, err := obrimCipherDecryptSalted([]byte(keyValue), salt, payload) if err != nil { return obrimCipherBuildResponse(false, "FAILED_DECRYPTION", nil) } if err := obrimCipherWriteOutput(outputPath, decryptedData); err != nil { return obrimCipherBuildResponse(false, "FAILED_OUTPUT_WRITE", nil) } return obrimCipherBuildResponse(true, "SUCCESS_DECRYPT_SALTED", map[string]any{ "operation": operationType, "algorithm": obrimCipherAlgorithm, "format": signature, "version": version, "inputPath": inputPath, "outputPath": outputPath, }) } return obrimCipherBuildResponse(false, "FAILED_UNKNOWN_OPERATION", nil) } // obrimCipherValidateType validates operation type. func obrimCipherValidateType(operationType string) error { switch operationType { case "encrypt-standard", "encrypt-salted", "decrypt-standard", "decrypt-salted": return nil default: return errors.New("invalid") } } // obrimCipherValidateConfig validates configuration. func obrimCipherValidateConfig(config map[string]any) error { if config == nil { return errors.New("invalid") } keyValue, keyExists := config["key"].(string) inputValue, inputExists := config["input"].(string) outputValue, outputExists := config["output"].(string) if !keyExists || !inputExists || !outputExists { return errors.New("invalid") } if keyValue == "" || inputValue == "" || outputValue == "" { return errors.New("invalid") } return nil } // obrimCipherValidateKey validates AES-256 key requirements. func obrimCipherValidateKey(key []byte) error { if len(key) != 32 { return errors.New("invalid") } return nil } // obrimCipherResolvePath resolves path to absolute path. func obrimCipherResolvePath(path string) (string, error) { return filepath.Abs(path) } // obrimCipherReadInput reads source file content. func obrimCipherReadInput(path string) ([]byte, error) { fileInfo, err := os.Stat(path) if err != nil { return nil, err } if fileInfo.IsDir() { return nil, errors.New("directory") } return os.ReadFile(path) } // obrimCipherWriteOutput writes processed content. func obrimCipherWriteOutput(path string, data []byte) error { directory := filepath.Dir(path) if err := os.MkdirAll(directory, 0755); err != nil { return err } tempFile, err := os.CreateTemp(directory, "obrim-cipher-*") if err != nil { return err } tempPath := tempFile.Name() if _, err := tempFile.Write(data); err != nil { _ = tempFile.Close() _ = os.Remove(tempPath) return err } if err := tempFile.Close(); err != nil { _ = os.Remove(tempPath) return err } if err := os.Rename(tempPath, path); err != nil { _ = os.Remove(tempPath) return err } return nil } // obrimCipherBuildResponse builds standardized response. func obrimCipherBuildResponse(statusValue bool, code string, payload any) map[string]any { response := obrimCipherResponse{ Status: statusValue, Code: code, Payload: payload, } encoded, _ := json.Marshal(response) var result map[string]any _ = json.Unmarshal(encoded, &result) return result } // obrimCipherEncryptStandard encrypts AES-256 data. func obrimCipherEncryptStandard(key []byte, data []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } // Mutable variable: nonce buffer. nonce := make([]byte, obrimCipherNonceSize) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } ciphertext := gcm.Seal(nil, nonce, data, nil) return append(nonce, ciphertext...), nil } // obrimCipherGenerateSalt generates cryptographic salt. func obrimCipherGenerateSalt() ([]byte, error) { // Mutable variable: salt buffer. salt := make([]byte, obrimCipherSaltSize) if _, err := io.ReadFull(rand.Reader, salt); err != nil { return nil, err } return salt, nil } // obrimCipherBuildHeader builds file header. func obrimCipherBuildHeader() []byte { return []byte(obrimCipherSignature + ":" + obrimCipherVersion + ":") } // obrimCipherEncryptSalted encrypts AES-256 data with embedded salt. func obrimCipherEncryptSalted(key []byte, salt []byte, data []byte) ([]byte, error) { derivedKey := sha256.Sum256(append(key, salt...)) encryptedData, err := obrimCipherEncryptStandard(derivedKey[:], data) if err != nil { return nil, err } header := obrimCipherBuildHeader() var buffer bytes.Buffer buffer.Write(header) buffer.Write(salt) buffer.Write(encryptedData) return buffer.Bytes(), nil } // obrimCipherDecryptStandard decrypts AES-256 encrypted data. func obrimCipherDecryptStandard(key []byte, data []byte) ([]byte, error) { if len(data) <= obrimCipherNonceSize { return nil, errors.New("invalid") } nonce := data[:obrimCipherNonceSize] payload := data[obrimCipherNonceSize:] block, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } return gcm.Open(nil, nonce, payload, nil) } // obrimCipherParseHeader parses header metadata. func obrimCipherParseHeader(data []byte) (string, string, error) { header := obrimCipherBuildHeader() if len(data) < len(header)+obrimCipherSaltSize { return "", "", errors.New("signature") } if !bytes.HasPrefix(data, header) { return "", "", errors.New("signature") } return obrimCipherSignature, obrimCipherVersion, nil } // obrimCipherExtractSalt extracts salt. func obrimCipherExtractSalt(data []byte) ([]byte, error) { headerLength := len(obrimCipherBuildHeader()) if len(data) < headerLength+obrimCipherSaltSize { return nil, errors.New("invalid") } return data[headerLength : headerLength+obrimCipherSaltSize], nil } // obrimCipherExtractPayload extracts encrypted payload. func obrimCipherExtractPayload(data []byte) ([]byte, error) { headerLength := len(obrimCipherBuildHeader()) offset := headerLength + obrimCipherSaltSize if len(data) <= offset { return nil, errors.New("invalid") } return data[offset:], nil } // obrimCipherDecryptSalted decrypts AES-256 encrypted data using salt. func obrimCipherDecryptSalted(key []byte, salt []byte, payload []byte) ([]byte, error) { derivedKey := sha256.Sum256(append(key, salt...)) return obrimCipherDecryptStandard(derivedKey[:], payload) }