util java8 ejemplo java encoding java-8 base64 mime

java8 - Migración de sun.misc.BASE64 a Java 8 java.util.Base64



java xml base64 encode (3)

Pregunta

¿Son el Java 8 java.util.Base64 MIME Encoder y Decoder un reemplazo sun.misc.BASE64Encoder API de Java interna no compatible sun.misc.BASE64Encoder y sun.misc.BASE64Decoder ?

Lo que pienso hasta ahora y por qué.

Basado en mi investigación y pruebas rápidas (ver código a continuación) , debería ser un reemplazo directo porque

  • sun.misc.BASE64Encoder basado en su JavaDoc es un codificador de caracteres BASE64 como se especifica en RFC1521 . Este RFC es parte de la especificación MIME ...
  • java.util.Base64 basado en su JavaDoc Utiliza el "Alfabeto Base64" como se especifica en la Tabla 1 de RFC 2045 para operaciones de codificación y decodificación ... bajo MIME

Suponiendo que no haya cambios significativos en el RFC 1521 y 2045 (no pude encontrar ninguno) y en base a mi prueba rápida con el Codificador / Decodificador MIME Java 8 Base64 debería estar bien.

Lo que busco

  • una fuente autorizada que confirme o desapruebe el punto de "reemplazo directo" O
  • un contraejemplo que muestra un caso donde java.util.Base64 tiene un comportamiento diferente al de sun.misc.BASE64Encoder OpenJDK Java 8 implementa (8u40-b25) (BASE64Decoder) O
  • lo que sea que pienses responde a la pregunta definitivamente

Para referencia

Mi codigo de prueba

public class Base64EncodingDecodingRoundTripTest { public static void main(String[] args) throws IOException { String test1 = " ~!@#$%^& *()_+=`| }{[]//;: /"?><,./ "; String test2 = test1 + test1; encodeDecode(test1); encodeDecode(test2); } static void encodeDecode(final String testInputString) throws IOException { sun.misc.BASE64Encoder unsupportedEncoder = new sun.misc.BASE64Encoder(); sun.misc.BASE64Decoder unsupportedDecoder = new sun.misc.BASE64Decoder(); Base64.Encoder mimeEncoder = java.util.Base64.getMimeEncoder(); Base64.Decoder mimeDecoder = java.util.Base64.getMimeDecoder(); String sunEncoded = unsupportedEncoder.encode(testInputString.getBytes()); System.out.println("sun.misc encoded: " + sunEncoded); String mimeEncoded = mimeEncoder.encodeToString(testInputString.getBytes()); System.out.println("Java 8 Base64 MIME encoded: " + mimeEncoded); byte[] mimeDecoded = mimeDecoder.decode(sunEncoded); String mimeDecodedString = new String(mimeDecoded, Charset.forName("UTF-8")); byte[] sunDecoded = unsupportedDecoder.decodeBuffer(mimeEncoded); // throws IOException String sunDecodedString = new String(sunDecoded, Charset.forName("UTF-8")); System.out.println(String.format("sun.misc decoded: %s | Java 8 Base64 decoded: %s", sunDecodedString, mimeDecodedString)); System.out.println("Decoded results are both equal: " + Objects.equals(sunDecodedString, mimeDecodedString)); System.out.println("Mime decoded result is equal to test input string: " + Objects.equals(testInputString, mimeDecodedString)); System.out.println("/n"); } }


Aquí hay un pequeño programa de prueba que ilustra una diferencia en las cadenas codificadas:

byte[] bytes = new byte[57]; String enc1 = new sun.misc.BASE64Encoder().encode(bytes); String enc2 = new String(java.util.Base64.getMimeEncoder().encode(bytes), StandardCharsets.UTF_8); System.out.println("enc1 = <" + enc1 + ">"); System.out.println("enc2 = <" + enc2 + ">"); System.out.println(enc1.equals(enc2));

Su salida es:

enc1 = <AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > enc2 = <AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA> false

Tenga en cuenta que la salida codificada de sun.misc.BASE64Encoder tiene una nueva línea al final. No siempre agrega una nueva línea, pero sucede que si la cadena codificada tiene exactamente 76 caracteres en su última línea. (El autor de java.util.Base64 consideró que se trataba de un pequeño error en la implementación de sun.misc.BASE64Encoder : consulte el hilo de revisión ).

Esto puede parecer una trivialidad, pero si tiene un programa que se basa en este comportamiento específico, los codificadores de conmutación pueden dar como resultado una salida con formato incorrecto. Por lo tanto, concluyo que java.util.Base64 no es un reemplazo sun.misc.BASE64Encoder para sun.misc.BASE64Encoder .

Por supuesto, la intención de java.util.Base64 es que es un reemplazo funcionalmente equivalente, compatible con RFC, de alto rendimiento, totalmente compatible y especificado que está diseñado para admitir la migración de código lejos de sun.misc.BASE64Encoder . Sin embargo, debe tener en cuenta algunos casos de borde como este al migrar.


No hay cambios en la especificación base64 entre rfc1521 y rfc2045.

Todas las implementaciones de base64 podrían considerarse reemplazos directos entre sí, las únicas diferencias entre las implementaciones de base64 son:

  1. el alfabeto utilizado
  2. los API proporcionados (por ejemplo, algunos pueden actuar solo en un búfer de entrada completo, mientras que otros pueden ser máquinas de estados finitos que le permiten continuar empujando fragmentos de entrada a través de ellos hasta que haya terminado).

El alfabeto MIME base64 se ha mantenido constante entre las versiones de RFC (debe o el software anterior se rompería) y es: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/

Como señala Wikipedia , solo los 2 últimos caracteres pueden cambiar entre las implementaciones de base64.

Como ejemplo de una implementación base64 que cambia los últimos 2 caracteres, la especificación IMAP MUTF-7 usa el siguiente alfabeto base64: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+,

El motivo del cambio es que el carácter / se usa a menudo como un delimitador de ruta y como la codificación MUTF-7 se usa para aplanar rutas de directorio no ASCII en ASCII, el carácter / debe ser evitado en los segmentos codificados.


Suponiendo que ambos codificadores están libres de errores, entonces el RFC requiere codificaciones distintas para cada secuencia de 0 bytes, 1 byte, 2 bytes y 3 bytes. Las secuencias más largas se dividen en tantas secuencias de 3 bytes como sea necesario, seguidas de una secuencia final. Por lo tanto, si las dos implementaciones manejan correctamente todas las secuencias posibles 16,843,009 (1 + 256 + 65536 + 16777216), entonces las dos implementaciones también son idénticas.

Estas pruebas solo tardan unos minutos en ejecutarse. Al cambiar ligeramente su código de prueba, lo hice y mi instalación de Java 8 pasó toda la prueba. Por lo tanto, la implementación pública se puede utilizar para reemplazar de forma segura la implementación de sun.misc.

Aquí está mi código de prueba:

import java.util.Base64; import java.util.Arrays; import java.io.IOException; public class Base64EncodingDecodingRoundTripTest { public static void main(String[] args) throws IOException { System.out.println("Testing zero byte encoding"); encodeDecode(new byte[0]); System.out.println("Testing single byte encodings"); byte[] test = new byte[1]; for(int i=0;i<256;i++) { test[0] = (byte) i; encodeDecode(test); } System.out.println("Testing double byte encodings"); test = new byte[2]; for(int i=0;i<65536;i++) { test[0] = (byte) i; test[1] = (byte) (i >>> 8); encodeDecode(test); } System.out.println("Testing triple byte encodings"); test = new byte[3]; for(int i=0;i<16777216;i++) { test[0] = (byte) i; test[1] = (byte) (i >>> 8); test[2] = (byte) (i >>> 16); encodeDecode(test); } System.out.println("All tests passed"); } static void encodeDecode(final byte[] testInput) throws IOException { sun.misc.BASE64Encoder unsupportedEncoder = new sun.misc.BASE64Encoder(); sun.misc.BASE64Decoder unsupportedDecoder = new sun.misc.BASE64Decoder(); Base64.Encoder mimeEncoder = java.util.Base64.getMimeEncoder(); Base64.Decoder mimeDecoder = java.util.Base64.getMimeDecoder(); String sunEncoded = unsupportedEncoder.encode(testInput); String mimeEncoded = mimeEncoder.encodeToString(testInput); // check encodings equal if( ! sunEncoded.equals(mimeEncoded) ) { throw new IOException("Input "+Arrays.toString(testInput)+" produced different encodings (sun=/""+sunEncoded+"/", mime=/""+mimeEncoded+"/")"); } // Check cross decodes are equal. Note encoded forms are identical byte[] mimeDecoded = mimeDecoder.decode(sunEncoded); byte[] sunDecoded = unsupportedDecoder.decodeBuffer(mimeEncoded); // throws IOException if(! Arrays.equals(mimeDecoded,sunDecoded) ) { throw new IOException("Input "+Arrays.toString(testInput)+" was encoded as /""+sunEncoded+"/", but decoded as sun="+Arrays.toString(sunDecoded)+" and mime="+Arrays.toString(mimeDecoded)); } } }