obrimbaseapi/essential/visible/service/helper/cipher/cipher.go

424 lines
11 KiB
Go

/*
|--------------------------------------------------------------------------
| Cipher
|--------------------------------------------------------------------------
|
| Name:
| - Cipher
|
| Purpose:
| - Provide reusable AES-256 encryption and decryption capabilities
| supporting standard and salted file formats through a unified
| interface.
|
| Guidelines:
| - Support only AES-256 operations.
| - Preserve input immutability.
| - Never expose sensitive values in error responses.
| - Fail safely without producing partial output.
|
| Examples:
| - ObrimCipher("encrypt-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"
"io"
"os"
"path/filepath"
"go/module/name/essential/external/service/helper/log"
"go/module/name/essential/external/service/helper/status"
)
const (
obrimCipherAlgorithm = "AES-256"
obrimCipherSignature = "OBRIMCIPHER"
obrimCipherVersion = "1"
obrimCipherSaltSize = 32
)
type obrimCipherResponse struct {
Status bool `json:"status"`
Code string `json:"code"`
Payload any `json:"payload"`
}
// Function Name: ObrimCipher
// Function Purpose: Execute encryption or decryption workflows.
func ObrimCipher(operationType string, config map[string]any) map[string]any {
log.ObrimLog("INIT", "Cipher utility started.")
if errCode := obrimCipherValidateType(operationType); errCode != "" {
return obrimCipherBuildResponse(false, errCode, nil)
}
if errCode := obrimCipherValidateConfig(config); errCode != "" {
return obrimCipherBuildResponse(false, errCode, nil)
}
keyValue := config["key"].(string)
if errCode := obrimCipherValidateKey(keyValue); errCode != "" {
return obrimCipherBuildResponse(false, errCode, 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_FILE_ACCESS", nil)
}
switch operationType {
case "encrypt-standard":
outputData, err := obrimCipherEncryptStandard(inputData, []byte(keyValue))
if err != nil {
return obrimCipherBuildResponse(false, "FAILED_ENCRYPTION", nil)
}
if err := obrimCipherWriteOutput(outputPath, outputData); err != nil {
return obrimCipherBuildResponse(false, "FAILED_OUTPUT_WRITE", nil)
}
status.ObrimStatus("SUCCESS", "Encryption completed.")
return obrimCipherBuildResponse(true, "SUCCESS_ENCRYPT_STANDARD", map[string]any{
"operation": operationType,
"algorithm": obrimCipherAlgorithm,
"inputPath": inputPath,
"outputPath": outputPath,
})
case "encrypt-salted":
outputData, err := obrimCipherEncryptSalted(inputData, []byte(keyValue))
if err != nil {
return obrimCipherBuildResponse(false, "FAILED_ENCRYPTION", nil)
}
if err := obrimCipherWriteOutput(outputPath, outputData); 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":
outputData, err := obrimCipherDecryptStandard(inputData, []byte(keyValue))
if err != nil {
return obrimCipherBuildResponse(false, "FAILED_DECRYPTION", nil)
}
if err := obrimCipherWriteOutput(outputPath, outputData); 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 {
return obrimCipherBuildResponse(false, "FAILED_SIGNATURE_VALIDATION", nil)
}
outputData, err := obrimCipherDecryptSalted(inputData, []byte(keyValue))
if err != nil {
return obrimCipherBuildResponse(false, "FAILED_DECRYPTION", nil)
}
if err := obrimCipherWriteOutput(outputPath, outputData); 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_OPERATION", nil)
}
// Function Name: obrimCipherValidateType
// Function Purpose: Validate operation type selection.
func obrimCipherValidateType(operationType string) string {
switch operationType {
case "encrypt-standard", "encrypt-salted", "decrypt-standard", "decrypt-salted":
return ""
default:
return "FAILED_TYPE_VALIDATION"
}
}
// Function Name: obrimCipherValidateConfig
// Function Purpose: Validate configuration values.
func obrimCipherValidateConfig(config map[string]any) string {
if config == nil {
return "FAILED_CONFIG_VALIDATION"
}
_, keyOK := config["key"].(string)
_, inputOK := config["input"].(string)
_, outputOK := config["output"].(string)
if !keyOK || !inputOK || !outputOK {
return "FAILED_CONFIG_VALIDATION"
}
return ""
}
// Function Name: obrimCipherValidateKey
// Function Purpose: Validate AES-256 compatible key requirements.
func obrimCipherValidateKey(key string) string {
if len([]byte(key)) != 32 {
return "FAILED_KEY_VALIDATION"
}
return ""
}
// Function Name: obrimCipherResolvePath
// Function Purpose: Resolve relative paths into absolute paths.
func obrimCipherResolvePath(path string) (string, error) {
return filepath.Abs(path)
}
// Function Name: obrimCipherReadInput
// Function Purpose: Read source file content.
func obrimCipherReadInput(path string) ([]byte, error) {
return os.ReadFile(path)
}
// Function Name: obrimCipherWriteOutput
// Function Purpose: Write processed output to destination file.
func obrimCipherWriteOutput(path string, data []byte) error {
tempPath := path + ".tmp"
if err := os.WriteFile(tempPath, data, 0600); err != nil {
return err
}
return os.Rename(tempPath, path)
}
// Function Name: obrimCipherBuildResponse
// Function Purpose: Build standardized utility responses.
func obrimCipherBuildResponse(statusValue bool, code string, payload any) map[string]any {
response := obrimCipherResponse{
Status: statusValue,
Code: code,
Payload: payload,
}
buffer, _ := json.Marshal(response)
var result map[string]any
_ = json.Unmarshal(buffer, &result)
return result
}
// Function Name: obrimCipherEncryptStandard
// Function Purpose: Encrypt data using AES-256 without salt metadata.
func obrimCipherEncryptStandard(data []byte, key []byte) ([]byte, error) {
return obrimCipherEncrypt(data, key)
}
// Function Name: obrimCipherGenerateSalt
// Function Purpose: Generate a cryptographically secure random salt.
func obrimCipherGenerateSalt() ([]byte, error) {
salt := make([]byte, obrimCipherSaltSize)
_, err := io.ReadFull(rand.Reader, salt)
return salt, err
}
// Function Name: obrimCipherBuildHeader
// Function Purpose: Build encrypted file signature and version header.
func obrimCipherBuildHeader() []byte {
return []byte(obrimCipherSignature + ":" + obrimCipherVersion + ":")
}
// Function Name: obrimCipherEncryptSalted
// Function Purpose: Encrypt data using AES-256 with embedded salt metadata.
func obrimCipherEncryptSalted(data []byte, key []byte) ([]byte, error) {
salt, err := obrimCipherGenerateSalt()
if err != nil {
return nil, err
}
derivedKey := sha256.Sum256(append(key, salt...))
ciphertext, err := obrimCipherEncrypt(data, derivedKey[:])
if err != nil {
return nil, err
}
output := bytes.Buffer{}
output.Write(obrimCipherBuildHeader())
output.Write(salt)
output.Write(ciphertext)
return output.Bytes(), nil
}
// Function Name: obrimCipherDecryptStandard
// Function Purpose: Decrypt AES-256 encrypted data without salt metadata.
func obrimCipherDecryptStandard(data []byte, key []byte) ([]byte, error) {
return obrimCipherDecrypt(data, key)
}
// Function Name: obrimCipherParseHeader
// Function Purpose: Parse encrypted file signature and version metadata.
func obrimCipherParseHeader(data []byte) (string, string, error) {
header := obrimCipherBuildHeader()
if len(data) < len(header) {
return "", "", os.ErrInvalid
}
if !bytes.Equal(data[:len(header)], header) {
return "", "", os.ErrInvalid
}
return obrimCipherSignature, obrimCipherVersion, nil
}
// Function Name: obrimCipherExtractSalt
// Function Purpose: Extract embedded salt from encrypted data.
func obrimCipherExtractSalt(data []byte) ([]byte, error) {
headerLength := len(obrimCipherBuildHeader())
if len(data) < headerLength+obrimCipherSaltSize {
return nil, os.ErrInvalid
}
return data[headerLength : headerLength+obrimCipherSaltSize], nil
}
// Function Name: obrimCipherExtractPayload
// Function Purpose: Extract encrypted payload from the encrypted file.
func obrimCipherExtractPayload(data []byte) ([]byte, error) {
headerLength := len(obrimCipherBuildHeader())
offset := headerLength + obrimCipherSaltSize
if len(data) <= offset {
return nil, os.ErrInvalid
}
return data[offset:], nil
}
// Function Name: obrimCipherDecryptSalted
// Function Purpose: Decrypt AES-256 encrypted data using extracted salt metadata.
func obrimCipherDecryptSalted(data []byte, key []byte) ([]byte, error) {
salt, err := obrimCipherExtractSalt(data)
if err != nil {
return nil, err
}
payload, err := obrimCipherExtractPayload(data)
if err != nil {
return nil, err
}
derivedKey := sha256.Sum256(append(key, salt...))
return obrimCipherDecrypt(payload, derivedKey[:])
}
// Function Name: obrimCipherEncrypt
// Function Purpose: Perform AES-256 GCM encryption.
func obrimCipherEncrypt(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Logic:
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
ciphertext := gcm.Seal(nil, nonce, data, nil)
return append(nonce, ciphertext...), nil
}
// Function Name: obrimCipherDecrypt
// Function Purpose: Perform AES-256 GCM decryption.
func obrimCipherDecrypt(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return nil, os.ErrInvalid
}
nonce := data[:nonceSize]
ciphertext := data[nonceSize:]
return gcm.Open(nil, nonce, ciphertext, nil)
}