Implementación de Seguridad en Transmisión de Documentos Electrónicos

  • Revisión del progreso de la semana anterior (enlaces de código, documentación)
  • Planificación para la semana actual

Progreso de la Semana Anterior

SM3 con Sal

Se implementó un utilitario para generar hashes SM3 con sal aleatoria, integrando la biblioteca BouncyCastle. La sal se genera de forma segura y se concatena con los datos antes de calcular el hash.


package com.example.security.util;

import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;

import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;

public class HashUtil {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static String hashWithSalt(String input, byte[] salt) {
        byte[] dataBytes = input.getBytes();
        byte[] combined = combineArrays(dataBytes, salt);
        byte[] hashBytes = computeSM3Hash(combined);
        return bytesToHex(hashBytes);
    }

    public static String generateSaltedHash(String plainText) {
        byte[] randomSalt = createSalt();
        String hashResult = hashWithSalt(plainText, randomSalt);
        return bytesToHex(randomSalt) + hashResult;
    }

    public static byte[] createSalt() {
        byte[] salt = new byte[16];
        new SecureRandom().nextBytes(salt);
        return salt;
    }

    private static byte[] combineArrays(byte[] arr1, byte[] arr2) {
        byte[] result = Arrays.copyOf(arr1, arr1.length + arr2.length);
        System.arraycopy(arr2, 0, result, arr1.length, arr2.length);
        return result;
    }

    private static byte[] computeSM3Hash(byte[] data) {
        SM3Digest digest = new SM3Digest();
        digest.update(data, 0, data.length);
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash, 0);
        return hash;
    }

    public static String bytesToHex(byte[] bytes) {
        return Hex.toHexString(bytes);
    }
}

Se configuró una tarea porgramada para actualizar contraseñas de usuario periódicamente, renueva la sal y recalcula el hash SM3. Se asume una contraseña predeterminada para pruebas, pero en producción se debe gestionar individualmente.


@Component
public class PasswordScheduler {

    private static final Logger logger = LoggerFactory.getLogger(PasswordScheduler.class);

    @Autowired
    private UserRepository userRepo;

    @Value("${app.default.password}")
    private String defaultPass;

    @Scheduled(fixedRate = 10000)
    public void rotatePasswords() {
        List<user> allUsers = userRepo.findAll();
        for (User user : allUsers) {
            byte[] newSalt = HashUtil.createSalt();
            String hashedPass = HashUtil.hashWithSalt(defaultPass, newSalt);
            user.setPasswordHash(hashedPass);
            user.setSaltHex(HashUtil.bytesToHex(newSalt));
            if (userRepo.save(user) != null) {
                logger.info("Contraseña actualizada para usuario {}", user.getUsername());
            } else {
                logger.error("Fallo al actualizar usuario {}", user.getUsername());
            }
        }
    }
}
</user>

SM4 para Cifrado

Los documentos se cifran usando SM4 en modo CBC con PKCS5Padding. Se genera una clave aleatoria y un vector de inicialización (IV) por operación. El IV se almacena concatenado con el texto cifrado para facilitar el descifrado.


package com.example.security.cipher;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;

public class DocumentCipher {

    private static final String ALGORITHM = "SM4";
    private static final String TRANSFORMATION = "SM4/CBC/PKCS5Padding";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static byte[] encryptData(byte[] plaintext, byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] ciphertext = cipher.doFinal(plaintext);
        return concatenate(iv, ciphertext);
    }

    public static byte[] decryptData(byte[] encrypted, byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
        byte[] iv = Arrays.copyOfRange(encrypted, 0, 16);
        byte[] ciphertext = Arrays.copyOfRange(encrypted, 16, encrypted.length);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return cipher.doFinal(ciphertext);
    }

    public static void encryptFile(String source, String target, String key) throws Exception {
        byte[] fileBytes = Files.readAllBytes(Paths.get(source));
        byte[] encrypted = encryptData(fileBytes, key.getBytes(StandardCharsets.UTF_8));
        Files.write(Paths.get(target), encrypted);
    }

    public static void decryptFile(String source, String target, String key) throws Exception {
        byte[] encryptedBytes = Files.readAllBytes(Paths.get(source));
        byte[] decrypted = decryptData(encryptedBytes, key.getBytes(StandardCharsets.UTF_8));
        Files.write(Paths.get(target), decrypted);
    }

    public static String createRandomKey(int length) {
        SecureRandom rand = new SecureRandom();
        StringBuilder keyBuilder = new StringBuilder();
        while (keyBuilder.length() < length) {
            int code = rand.nextInt(91);
            if ((code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122)) {
                keyBuilder.append((char) code);
            }
        }
        return keyBuilder.toString();
    }

    private static byte[] concatenate(byte[] a, byte[] b) {
        byte[] result = new byte[a.length + b.length];
        System.arraycopy(a, 0, result, 0, a.length);
        System.arraycopy(b, 0, result, a.length, b.length);
        return result;
    }
}

Las claves se almacenan de forma invertida en la base de datos por seguridad. Se implementó una tarea para renovar claves cada tres días, descifra documentos con la clave antigua y los recifra con una nueva clave generada aleatoriamente.


@Component
public class KeyRotationTask {

    private static final Logger log = LoggerFactory.getLogger(KeyRotationTask.class);

    @Autowired
    private DocumentService docService;

    @Scheduled(fixedRate = 259200000)
    public void rotateKeys() {
        try {
            List<document> encryptedDocs = docService.getEncryptedDocuments();
            for (Document doc : encryptedDocs) {
                String oldKey = new StringBuilder(doc.getKeyStored()).reverse().toString();
                String path = doc.getFilePath();
                byte[] encData = Files.readAllBytes(Paths.get(path));
                byte[] decData = DocumentCipher.decryptData(encData, oldKey.getBytes(StandardCharsets.UTF_8));
                String newKey = DocumentCipher.createRandomKey(16);
                byte[] reEncData = DocumentCipher.encryptData(decData, newKey.getBytes(StandardCharsets.UTF_8));
                Files.write(Paths.get(path), reEncData);
                doc.setKeyStored(new StringBuilder(newKey).reverse().toString());
                docService.update(doc);
                log.info("Clave renovada para documento {}", doc.getId());
            }
        } catch (Exception e) {
            log.error("Error en renovación de claves", e);
        }
    }
}
</document>

Plan para la Semana Actual

  • Esperar la aprobación del progreso actual
  • Continuar mejorando los algoritmos de gestión de claves

Etiquetas: SM3 SM4 cifrado Hash sal

Publicado el 6-28 00:10