Criptografia a Java (JCA)

Els motors criptogràfics de Java proporcionen mecanismes per signatures digitals, resums de missatges, etc. Aquests motors estan implementats per proveïdors de seguretat (java.security.Provider), que es poden visualitzar mitjançant java.security.Security.getProviders(). Cada motor (java.security.Provider.Service) té un tipus i un algorisme.

Claus

Podem generar claus de tipus simètric (KeyGenerator) o asimètric (KeyPairGenerator).

KeyGenerator keyGen = KeyGenerator.getInstance(algorithm); keyGen.init(size); SecretKey secretKey = keyGen.generateKey();

Algorismes simètrics típics són DES (56 bits) o AES (128, 192, 256 bits).

KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm); kpg.initialize(size); KeyPair kp = kpg.generateKeyPair(); PublicKey publicKey = kp.getPublic(); PrivateKey privateKey = kp.getPrivate();

Cal indicar l'algorisme. El més habitual és RSA (1024, 2048 bits).

Tant SecretKey, com PublicKey i PrivateKey, són subclasses de java.security.Key. Totes elles tenen un mètode getEncoded(): la clau en format binari.

Xifrat

Per a poder xifrar, necessitem un objecte javax.crypto.Cipher:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

El format del paràmetre (transformation) és algorithm/mode/padding. Mode i padding són opcionals: si no s'indiquen, s'utilitza el mode i padding per defecte. Què són?

  • Padding: és una tècnica que consisteix a afegir dades de farciment al començament, mig o fi d'un missatge abans de ser xifrat. Això es fa perquè els algorismes estan dissenyats per tenir dades d'entrada d'una mida concreta.
  • Mode: defineix com es fa la correspondència entre els blocs d'entrada (en pla) i els de sortida (xifrats). El més senzill és el mode ECB: un bloc d'entrada va a un de sortida. Altres modes, més segurs, fan aleatòria aquesta correspondència.

Després, hem d'inicialitzar l'objecte utilitzant el mode (Cipher.ENCRYPT_MODE o Cipher.DECRYPT_MODE) i la clau de xifrat:

cipher.init(Cipher.ENCRYPT_MODE, key); // xifrat cipher.init(Cipher.DECRYPT_MODE, key); // desxifrat

Finalment, realitzem el xifrat o desxifrat:

byte[] bytesOriginal = textOriginal.getBytes("UTF-8"); // necessito bytes com a entrada byte[] bytesXifrat = cipher.doFinal(bytesOriginal);

El desxifrat podria ser:

byte[] bytesDesxifrat = cipher.doFinal(bytesXifrat); // alternativament, si el contingut a desxifrar és una part de l'array: byte[] bytesDesxifrat = cipher.doFinal(bytesXifrat, inici, longitud);

Alguns modes de xifrat en bloc utilitzen el que s'anomena vector d'inicialització (IV). Es tracta d'un paràmetre aleatori per a l'algorisme de xifrat que fa més difícil relacionar els blocs en funció de les seves entrades. Normalment no cal que sigui secret, només que no es repeteixi amb la mateixa clau.

Per utilitzar aquests modes (p.ex. CBC), cal afegir un nou paràmetre quan s'inicialitza el Cipher. La mida de l'IV és habitualment la mateixa del bloc. Per AES és de 16 bytes (256 bits).

byte[] iv; // inicialitzar iv amb un generador aleatori com SecureRandom IvParameterSpec parameterSpec = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec); // xifrat cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec); // desxifrat

Xifrat de streams

No té sentit xifrar grans quantitats de dades amb mètodes de bloc. Per a aquestes situacions, podem utilitzar el xifrat de streams. Aquest xifrat és sempre simètric.

A Java, tenim les classes CipherInputStream i CipherOutputStream.

CipherInputStream i CipherOutputStream admeten un Cipher simètric de bloc, com per exemple AES/ECB/PKCS5Padding, o bé els modes de tipus feedback CFB8 or OFB8, (8 = blocs de 8 bits), com per exemple AES/CFB8/NoPadding. Els modes feedback necessiten vectors d'inicialització (IV).

Per exemple, si volem obrir un arxiu i xifrar-lo o desxifrar-lo, podem fer-ho així:

FileInputStream in = new FileInputStream(inputFilename); FileOutputStream fileOut = new FileOutputStream(outputFilename); CipherOutputStream out = new CipherOutputStream(fileOut, cipher);

Llavors, caldria copiar el stream in a out.

L'objecte cipher ha d'inicialitzar-se amb el mode que calgui, ENCRYPT_MODE o DECRYPT_MODE.

CipherInputStream es pot utilitzar de forma anàloga. En aquest cas, el stream de sortida podria ser un FileOutputStream:

FileInputStream fileIn = new FileInputStream(inputFilename); CipherInputStream in = new CipherInputStream(fileIn, cipher); FileOutputStream fileOut = new FileOutputStream(outputFilename);

Dades binàries en text

Les claus i la informació xifrada està en format binari. Si cal intercanviar-ho utilitzant un canal de text, es poden convertir utilitzant Base64. A Java, tenim java.util.Base64.

byte[] binary1 = ...; String string = Base64.getEncoder().encodeToString(binary1); byte[] binary2 = Base64.getDecoder().decode(string); // binary1 i binary2 són iguals