Codificación y decodificación de cadenas Base64 en Java

En este tutorial, codificaremos y decodificaremos cadenas Base64 en Java con ejemplos. También realizaremos la codificación Base64 usando Apache Commons.

Introducción

El proceso de convertir datos mediante la aplicación de algunas técnicas/reglas en un nuevo formato se denomina codificación. Decodificación es el proceso inverso a la codificación: devolver los datos codificados al formato original.

La codificación nos rodea y las computadoras dependen en gran medida de diferentes formatos de codificación para entregar y transferir información.

Desde la última imagen del gato en tu feed hasta el mensaje de voz que escuchaste en una aplicación de mensajería, todos estos fueron codificados en el lado del remitente, entregados y decodificados en el lado del destinatario.

La codificación asegura que los datos permanezcan intactos y eficientes para el transporte.

¿Qué es Base64?

Base64 es un esquema de codificación de binario a texto. Se representa como caracteres ASCII imprimibles donde cada carácter Base64 contiene 6 bits de información binaria.

Es muy útil para almacenar información de imagen/audio en cadenas de información. Lo que Base64 no es es un algoritmo de cifrado.

Si bien "oscurece" los datos hasta cierto punto, no es un formato seguro de ninguna manera.

¿Cómo funciona Base64?

En Base64, como sugiere el nombre, se utilizan 64 caracteres para codificar datos binarios. Estos personajes son:

  • 26 Mayúsculas [A-Z]
  • 26 letras minúsculas [a-z]
  • 10 dígitos [0-9]
  • 2 caracteres especiales [+ , /]

Nota: También hay un carácter 65 (=), que tiene un significado especial y se llama carácter de relleno.

Como de costumbre, cuando se trata de computadoras, los números se representan en potencias de 2. Entonces, 64 caracteres de Base64 se pueden escribir como:
$$
64 = 2^6
$$
Cada carácter se representa en 6 bits. Podrías ir más alto que esto y hacer que cada carácter represente 8 bits. Eso sería Base256. Sin embargo, esto no es práctico debido a las dificultades en el proceso de codificación/descodificación.

Avancemos y codifiquemos manualmente una cadena simple para tener una buena idea de cómo funciona el proceso.

Codificación manual

Por ejemplo, digamos que tenemos una cadena - ABC123 que nos gustaría codificar en Base64. Hay cinco pasos que tomamos durante este proceso:

  1. Tome los valores ASCII de los caracteres de entrada por separado:

Carácter A B C 1 2 3 ASCII (decimales) 65 66 67 49 50 51


  1. Convierta el valor decimal de cada carácter en un binario de 8 bits:

Carácter A B C 1 2 3 ASCII 65 66 67 49 50 51 Binario 01000001 01000010 01000011 00110001 00110010 00110011


  1. Reorganice los números binarios en fragmentos de 6 bits:

    010000 010100 001001 000011 001100 010011 001000 110011

    If it's impossible to break it up into chunks of six, we have to almohadilla the sequence.

  2. Obtenga los valores decimales de estos fragmentos binarios:


Binario 010000 010100 001001 000011 001100 010011 001000 110011 Decimales 16 20 9 3 12 19 8 51


  1. Finalmente, utilizando el gráfico Base64, convierta cada número decimal en un carácter Base64:

Valor Char Valor Char Valor Char Valor Char 0 A 16 Q 32 g 48 w 1 B 17 D 33 h 49 x 2 C 18 S 34 i 50 a 3 D 19 T 35 j 51 z 4 E 20 U 36 k 52 0 5 F 21 V 37 l 53 1 6 G 22 W 38 m 54 2 7 H 23 X 39 n 55 3 8 I 24 Y 40 o 56 4 9 J 25 Z 41 p 57 5 10 K 26 a 42 q 58 6 11 L 27 b 43 der 59 7 12 M 28 c 44 s 60 8 13 N 29 d 45 t 61 9 14 O 30 y 46 u 62 + 15 P 31 f 47 v 63 /


Al final, nos quedamos con la cadena QUJDMTIz - la representación Base64 de ABC123.

Nota: 6 caracteres de entrada se codifican en 8 caracteres al final. Esto significa que se necesita ~33% más de espacio para almacenar una cadena codificada en Base64 en comparación con la cadena original.

¿Por qué usar la codificación Base64? {#por qué usar la codificación base64}

Las computadoras funcionan con 0s y 1s, entonces, ¿por qué molestarse en convertir esto a otro formato?

Si verdad. Binario es el lenguaje de las computadoras. Eso es exactamente por qué lo estamos convirtiendo. Una secuencia como 0010110 puede significar muchas cosas. Puede ser parte de una imagen, puede ser parte de un archivo de audio o puede ser un comando que borre la mitad de su disco duro.

Estos datos deben ser procesados ​​de manera diferente, dependiendo de lo que se supone que representan. Además, muchos servidores no esperan datos binarios sin procesar. Los servidores de correo electrónico, por ejemplo, esperan datos de texto. Todos los correos electrónicos se codifican antes de que las aplicaciones los envíen.

Codificación de cadenas con Java 8 - Base64

Java 8 nos presentó una nueva clase: Base64. Admite la codificación y decodificación de algunos tipos de variantes de datos según lo especificado por [RFC 2045] (https://www.ietf.org/rfc/rfc2045.tx) y [RFC 4648] (https://www.ietf.org /rfc/rfc4648.txt):

  • Básico
  • URL y nombre de archivo seguro
  • MIMO

Codificación y decodificación de cadenas básicas {#codificación y decodificación de cadenas básicas}

Usando el codificador base, podemos codificar una cadena en Base64. El codificador base no agrega un carácter separador de línea.

Si la longitud de la cadena no es divisible por 3 (la representación de 8 bits no podrá agruparse en grupos de 6 bits), el relleno se agregará en forma de = al final:

1
2
3
4
5
Encoder encoder = Base64.getEncoder();
String originalString = "basic";
String encodedString = encoder.encodeToString(originalString.getBytes());

System.out.println(encodedString);

Base64 es una clase de fábrica de la que recuperaremos diferentes implementaciones/variaciones del codificador. El método encodeToString() codificará nuestra entrada en una representación Base64 de la entrada y la empaquetará en una cadena. También puede usar el método encode() para codificarlo en un flujo byte o ByteBuffer.

Si es necesario, también puede devolver una instancia de codificador que no rellenará las cadenas de entrada a través del método sin Padding () mientras lo crea desde la clase de fábrica:

1
Base64.getEncoder().withoutPadding();

Nota: La codificación sin relleno probablemente dará como resultado una cadena Base64 que no se puede decodificar ya que se pierde una parte de la información.

Ejecutar el ejemplo de código producirá:

1
YmFzaWM=

Con nuestro String codificado, instanciamos también un Decodificador y decodificamos el String producido. Sin embargo, los decodificadores solo funcionan con matrices byte:

1
2
3
4
Decoder decoder = Base64.getDecoder();
byte[] bytes = decoder.decode(encodedString);
        
System.out.println(new String(bytes));

Este código dará como resultado:

1
basic

Codificación y decodificación de URL

Para codificar y decodificar URL, podemos usar una instancia de Encoder y Decoder que utilizan el tipo seguro de URL y nombre de archivo de Base64. Tampoco agregan un carácter separador de línea.

Los obtenemos a través de los métodos getUrlEncoder() y getUrlDecoder() a través de la fábrica Base64:

1
2
3
4
5
Encoder encoder = Base64.getUrlEncoder();
String originalinput = "https://wikihtp.com/tag/java/";
String encodedUrl = encoder.encodeToString(originalinput.getBytes());

System.out.println(encodedUrl);

Al igual que antes, el método encodeToString() codificará la entrada y la empaquetará en una cadena:

1
aHR0cHM6Ly9zdGFja2FidXNlLmNvbS90YWcvamF2YS8=

El método encode() codificaría la entrada en una matriz byte. De nuevo, el método decode() funciona con una matriz byte y decodifica la cadena Base64 en la original:

1
2
3
4
Decoder decoder = Base64.getUrlDecoder();
byte[] bytes = decoder.decode(encodedUrl);

System.out.println(new String(bytes));

Ejecutar este código produce:

1
https://wikihtp.com/tag/java/

Nota: Hay tres caracteres especiales disponibles en este esquema: +, /, =. Estos caracteres pueden tener un significado diferente en las URL y ciertos sistemas de archivos y podrían causar un problema si se usa la cadena codificada. Para asegurarnos de evitar estos problemas, las URL y las cadenas de archivo deben codificarse y decodificarse con el decodificador seguro para URL.

Los caracteres + y / se reemplazan con - y _ respectivamente.

Codificación y decodificación MIME

Las etiquetas Extensión de correo de Internet multipropósito (MIME) se utilizan para identificar el tipo de medio (HTML, audio, video).

Dado que muchos tipos de medios, como imágenes, documentos, etc., se envían como archivos adjuntos por correo electrónico, es necesario codificarlos en un formato aceptable para todos los protocolos.

Al enviar archivos adjuntos, como un archivo .txt, se codifican en Base64 y se adjuntan al correo electrónico. Así es como se ve un archivo adjunto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Content-Type: text/plain;
name="log_attachment.txt"
Content-Disposition: attachment;
filename="attachment.txt"
Content-Transfer-Encoding: base64

TUlNRS1WZXJzaW9uOiAxLjANClgtTWFpbGVyOiBNYWlsQmVlLk5FVCA4LjAuNC40MjgNClN1Ympl
Y3Q6IHRlc3Qgc3ViamVjdA0KVG86IGtldmlubUBkYXRhbW90aW9uLmNvbQ0KQ29udGVudC1UeXBl
OiBtdWx0aXBhcnQvYWx0ZXJuYXRpdmU7DQoJYm91bmRhcnk9Ii0tLS09X05leHRQYXJ0XzAwMF9B
RTZCXzcyNUUwOUFGLjg4QjdGOTM0Ig0KDQoNCi0tLS0tLT1fTmV4dFBhcnRfMDAwX0FFNkJfNzI1
RTA5QUYuODhCN0Y5MzQNCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsNCgljaGFyc2V0PSJ1dGYt
OCINCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IHF1b3RlZC1wcmludGFibGUNCg0KdGVzdCBi
b2R5DQotLS0tLS09X05leHRQYXJ0XzAwMF9BRTZCXzcyNUUwOUFGLjg4QjdGOTM0DQpDb250ZW50
LVR5cGU6IHRleHQvaHRtbDsNCgljaGFyc2V0PSJ1dGYtOCINCkNvbnRlbnQtVHJhbnNmZXItRW5j
b2Rpbmc6IHF1b3RlZC1wcmludGFibGUNCg0KPHByZT50ZXN0IGJvZHk8L3ByZT4NCi0tLS0tLT1f
TmV4dFBhcnRfMDAwX0FFNkJfNzI1RTA5QUYuODhCN0Y5MzQtLQ0K

Al codificar MIME, la salida se formatea para que sea compatible con MIME: las líneas no pueden tener más de 76 caracteres y cada línea termina con \r\n, excepto la última, naturalmente. \r es un carácter de retorno de carro mientras que \n es el carácter de nueva línea.

Hagamos un archivo llamado file.txt que contenga:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
"I met a traveller from an antique land,
Who said - Two vast and trunkless legs of stone
Stand in the desert.... Near them, on the sand,
Half sunk a shattered visage lies, whose frown,
And wrinkled lip, and sneer of cold command,
Tell that its sculptor well those passions read
Which yet survive, stamped on these lifeless things,
The hand that mocked them, and the heart that fed;
And on the pedestal, these words appear:
My name is Ozymandias, King of Kings;
Look on my Works, ye Mighty, and despair!
Nothing beside remains. Round the decay
Of that colossal Wreck, boundless and bare
The lone and level sands stretch far away.

Ozymandias, by Percy Bysshe Shelley

Ahora, vamos a leer los bytes del archivo y empaquetarlos en una matriz byte, después de lo cual lo codificaremos:

1
2
3
byte[] bytes = Files.readAllBytes(Paths.get("path/to/file"));
String encodedString = Base64.getMimeEncoder().encodeToString(bytes);
System.out.println(encodedString);

Este código nos arrojará:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
IkkgbWV0IGEgdHJhdmVsbGVyIGZyb20gYW4gYW50aXF1ZSBsYW5kLA0KV2hvIHNhaWTigJTigJxU
d28gdmFzdCBhbmQgdHJ1bmtsZXNzIGxlZ3Mgb2Ygc3RvbmUNClN0YW5kIGluIHRoZSBkZXNlcnQu
Li4uIE5lYXIgdGhlbSwgb24gdGhlIHNhbmQsDQpIYWxmIHN1bmsgYSBzaGF0dGVyZWQgdmlzYWdl
IGxpZXMsIHdob3NlIGZyb3duLA0KQW5kIHdyaW5rbGVkIGxpcCwgYW5kIHNuZWVyIG9mIGNvbGQg
Y29tbWFuZCwNClRlbGwgdGhhdCBpdHMgc2N1bHB0b3Igd2VsbCB0aG9zZSBwYXNzaW9ucyByZWFk
DQpXaGljaCB5ZXQgc3Vydml2ZSwgc3RhbXBlZCBvbiB0aGVzZSBsaWZlbGVzcyB0aGluZ3MsDQpU
aGUgaGFuZCB0aGF0IG1vY2tlZCB0aGVtLCBhbmQgdGhlIGhlYXJ0IHRoYXQgZmVkOw0KQW5kIG9u
IHRoZSBwZWRlc3RhbCwgdGhlc2Ugd29yZHMgYXBwZWFyOg0KTXkgbmFtZSBpcyBPenltYW5kaWFz
LCBLaW5nIG9mIEtpbmdzOw0KTG9vayBvbiBteSBXb3JrcywgeWUgTWlnaHR5LCBhbmQgZGVzcGFp
ciENCk5vdGhpbmcgYmVzaWRlIHJlbWFpbnMuIFJvdW5kIHRoZSBkZWNheQ0KT2YgdGhhdCBjb2xv
c3NhbCBXcmVjaywgYm91bmRsZXNzIGFuZCBiYXJlDQpUaGUgbG9uZSBhbmQgbGV2ZWwgc2FuZHMg
c3RyZXRjaCBmYXIgYXdheS7igJ0NCg0KT3p5bWFuZGlhcywgYnkgUGVyY3kgQnlzc2hlIFNoZWxs
ZXk=

Naturalmente, decodificar este contenido es tan fácil como:

1
2
3
Decoder decoder = Base64.getMimeDecoder();
byte[] decodedBytes = decoder.decode(encodedString);
System.out.println(new String(decodedBytes));

Este código producirá:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
"I met a traveller from an antique land,
Who said - Two vast and trunkless legs of stone
Stand in the desert.... Near them, on the sand,
Half sunk a shattered visage lies, whose frown,
And wrinkled lip, and sneer of cold command,
Tell that its sculptor well those passions read
Which yet survive, stamped on these lifeless things,
The hand that mocked them, and the heart that fed;
And on the pedestal, these words appear:
My name is Ozymandias, King of Kings;
Look on my Works, ye Mighty, and despair!
Nothing beside remains. Round the decay
Of that colossal Wreck, boundless and bare
The lone and level sands stretch far away.

Ozymandias, by Percy Bysshe Shelley

Apache Commons

Debido a su utilidad y prevalencia en Java, muchos proyectos tienen Apache Commons incluido en el classpath. Es una gran biblioteca con muchas características útiles que se usan a menudo en producción, y la codificación/descodificación no es una excepción.

Usando Maven, agregarlo a su proyecto es tan fácil como incluir la dependencia:

1
2
3
4
5
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>${version}</version>
</dependency>

O, si estás usando Gradle:

1
compile group: 'commons-codec', name: 'commons-codec', version: '${version}'

Similar a la implementación oficial de Java, la clase Base64 es la API principal. Sin embargo, en lugar de usarse como una fábrica para las instancias de Encoder/Decoder, la clase misma maneja toda la codificación y decodificación. Otros métodos útiles que proporciona son métodos como isBase64(), isInAlphabet() y isUrlSafe().

Codificación y decodificación de cadenas básicas de Apache Commons

Avancemos y codifiquemos una cadena simple en Base64. El método encodeBase64() acepta una matriz byte y devuelve una matriz byte, que representa la entrada codificada.

1
2
3
4
String inputString = "Some String";
byte[] bytes = Base64.encodeBase64(inputString.getBytes());

System.out.println(new String(bytes));

Ejecutar este código producirá:

1
U29tZSBTdHJpbmc=

Alternativamente, puede usar el método encodeBase64String(), que devolverá una cadena, en lugar de una matriz byte:

1
2
3
4
String inputString = "Some String";
String encodedString = Base64.encodeBase64String(inputString.getBytes());

System.out.println(encodedString);

Ejecutar este código producirá:

1
U29tZSBTdHJpbmc=

Del mismo modo, la clase Base64 también se utiliza como decodificador. Acepta una matriz byte o String y devuelve una matriz byte:

1
2
3
4
5
String decodedString = new String(Base64.decodeBase64(encodedString.getBytes()));
System.out.println(decodedString);
// Or
String anotherDecodedString = new String(Base64.decodeBase64(encodedString));
System.out.println(anotherDecodedString);

Ejecutar este código producirá:

1
2
Some String
Some String

Codificación y decodificación de URL de Apache Commons

La codificación y decodificación de URL sigue el mismo principio:

1
2
3
4
String originalInput = "https://wikihtp.com/tag/java/";
byte[] bytes = Base64.encodeBase64URLSafe(originalInput.getBytes());

System.out.println(new String(bytes));

Ejecutar esto resultará en:

1
aHR0cHM6Ly9zdGFja2FidXNlLmNvbS90YWcvamF2YS8=

También puede devolver una cadena en lugar de una matriz byte:

1
2
3
4
String originalInput = "https://wikihtp.com/tag/java/";
String encodedString = Base64.encodeBase64URLSafeString(originalInput.getBytes());

System.out.println(encodedString);

Los contenidos de encodedString son:

1
aHR0cHM6Ly9zdGFja2FidXNlLmNvbS90YWcvamF2YS8=

Y finalmente, también podemos decodificar la representación Base64 de la URL:

1
2
3
4
5
String decodedUrl = new String(Base64.decodeBase64(encodedString.getBytes()));
System.out.println(decodedUrl);
// Or
String anotherDecodedUrl = Base64.decodeBase64(encodedString);
System.out.println(anotherDecodedUrl);
1
2
https://wikihtp.com/tag/java/
https://wikihtp.com/tag/java/

Apache Commons MIME Codificación y decodificación

La codificación y decodificación de tipos MIME es un poco diferente a la implementación oficial de Java. Esta vez, usamos el método encodeBase64Chunked():

1
2
3
byte[] bytes = Files.readAllBytes(Paths.get("path/to/file"));
String encodedString = new String(Base64.encodeBase64Chunked(bytes));
System.out.println(encodedString);

Este código da como resultado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
IkkgbWV0IGEgdHJhdmVsbGVyIGZyb20gYW4gYW50aXF1ZSBsYW5kLA0KV2hvIHNhaWTigJTigJxU
d28gdmFzdCBhbmQgdHJ1bmtsZXNzIGxlZ3Mgb2Ygc3RvbmUNClN0YW5kIGluIHRoZSBkZXNlcnQu
Li4uIE5lYXIgdGhlbSwgb24gdGhlIHNhbmQsDQpIYWxmIHN1bmsgYSBzaGF0dGVyZWQgdmlzYWdl
IGxpZXMsIHdob3NlIGZyb3duLA0KQW5kIHdyaW5rbGVkIGxpcCwgYW5kIHNuZWVyIG9mIGNvbGQg
Y29tbWFuZCwNClRlbGwgdGhhdCBpdHMgc2N1bHB0b3Igd2VsbCB0aG9zZSBwYXNzaW9ucyByZWFk
DQpXaGljaCB5ZXQgc3Vydml2ZSwgc3RhbXBlZCBvbiB0aGVzZSBsaWZlbGVzcyB0aGluZ3MsDQpU
aGUgaGFuZCB0aGF0IG1vY2tlZCB0aGVtLCBhbmQgdGhlIGhlYXJ0IHRoYXQgZmVkOw0KQW5kIG9u
IHRoZSBwZWRlc3RhbCwgdGhlc2Ugd29yZHMgYXBwZWFyOg0KTXkgbmFtZSBpcyBPenltYW5kaWFz
LCBLaW5nIG9mIEtpbmdzOw0KTG9vayBvbiBteSBXb3JrcywgeWUgTWlnaHR5LCBhbmQgZGVzcGFp
ciENCk5vdGhpbmcgYmVzaWRlIHJlbWFpbnMuIFJvdW5kIHRoZSBkZWNheQ0KT2YgdGhhdCBjb2xv
c3NhbCBXcmVjaywgYm91bmRsZXNzIGFuZCBiYXJlDQpUaGUgbG9uZSBhbmQgbGV2ZWwgc2FuZHMg
c3RyZXRjaCBmYXIgYXdheS7igJ0NCg0KT3p5bWFuZGlhcywgYnkgUGVyY3kgQnlzc2hlIFNoZWxs
ZXk=

Decodificar esto es lo mismo que antes:

1
2
3
4
5
String decodedMime = new String(Base64.decodeBase64(encodedString.getBytes()));
System.out.println(decodedMime);
// Or
String anotherDecodedMime = Base64.decodeBase64(encodedString);
System.out.println(anotherDecodedMime);

Este código da como resultado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
"I met a traveller from an antique land,
Who said - Two vast and trunkless legs of stone
Stand in the desert.... Near them, on the sand,
Half sunk a shattered visage lies, whose frown,
And wrinkled lip, and sneer of cold command,
Tell that its sculptor well those passions read
Which yet survive, stamped on these lifeless things,
The hand that mocked them, and the heart that fed;
And on the pedestal, these words appear:
My name is Ozymandias, King of Kings;
Look on my Works, ye Mighty, and despair!
Nothing beside remains. Round the decay
Of that colossal Wreck, boundless and bare
The lone and level sands stretch far away.

Ozymandias, by Percy Bysshe Shelley

Métodos auxiliares de Apache Commons

Al más puro estilo de Apache Commons, se nos presentan algunos métodos de ayuda que nos hacen la vida un poco más fácil. Por ejemplo, podemos verificar si una cadena o una matriz byte están en Base64 válido:

1
2
3
4
5
6
String originalInput = "https://wikihtp.com/tag/java/";
byte[] bytes = Base64.encodeBase64URLSafe(originalInput.getBytes());

System.out.println(Base64.isBase64(bytes));
// Or
System.out.println(Base64.isBase64(encodedString));

Este código daría como resultado:

1
2
true 
true

Podemos verificar si estamos en modo seguro para URL o no:

1
System.out.println(Base64.isUrlSafe());

Dado que hemos codificado una cadena de URL a través del método encodeBase64URLSafe(), este código da como resultado:

1
true

Y finalmente, podemos verificar si cada byte individual de nuestra matriz está en el alfabeto Base64:

1
2
3
for (int i = 0; i < bytes.length; i++) {
    System.out.println(Base64.isInAlphabet(bytes[i]));
}

Conclusión

En este artículo, nos hemos sumergido en lo que es la codificación y decodificación, seguido de una descripción general de Base64. Hemos codificado manualmente una cadena para obtener una mejor comprensión del proceso, precediendo a muchos ejemplos de código.

El uso de la clase Base64 en Java nos permite crear diferentes tipos de codificadores y decodificadores, optimizados para codificar y decodificar cadenas básicas, URL y tipos MIME.

Usando la clase Base64 proporcionada por Apache Commons, confiamos en la clase base para realizar la codificación directamente.