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

448 lines
12 KiB
Go

/*
|--------------------------------------------------------------------------
| 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)
}