Em Java, como faço para converter uma matriz de bytes em uma seqüência de dígitos hexadecimais, mantendo zeros à esquerda?
Pergunta
Eu estou trabalhando com algum exemplo de código java para fazer hashes MD5. Uma parte converte os resultados de bytes para uma cadeia de dígitos hexadecimais:
byte messageDigest[] = algorithm.digest();
StringBuffer hexString = new StringBuffer();
for (int i=0;i<messageDigest.length;i++) {
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
}
No entanto, ele não funciona muito bem desde toHexString aparentemente cai zeros à esquerda. Então, qual é a maneira mais simples para ir de matriz de bytes para string hexadecimal que mantém os zeros à esquerda?
Solução
Uma abordagem simples seria a de verificar quantos dígitos são emitidos por Integer.toHexString()
e adicionar um zero a cada byte, se necessário. Algo parecido com isto:
public static String toHexString(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
Outras dicas
Confira Apache Commons Codec Hex.encodeHex . O tipo de retorno é char[]
, que pode ser convertido para trivialmente String
. Assim:
import org.apache.commons.codec.binary;
Hex.encodeHexString(messageDigest);
Você pode usar a abaixo. I testado neste levando com zero bytes e com inicial negativa bytes bem
public static String toHex(byte[] bytes) {
BigInteger bi = new BigInteger(1, bytes);
return String.format("%0" + (bytes.length << 1) + "X", bi);
}
Se você quiser dígitos hexadecimais minúsculas, uso "x"
no formato de string.
Use DatatypeConverter.printHexBinary()
. Você pode ler a sua documentação em http://docs.oracle.com/ JavaSE / 6 / docs / api / javax / XML / ligamento / DatatypeConverter.html
Por exemplo:
byte bytes[] = {(byte)0, (byte)0, (byte)134, (byte)0, (byte)61};
System.out.println(javax.xml.bind.DatatypeConverter.printHexBinary(bytes));
resultará em:
000086003D
Eu gostava apresentações de Steve, mas ele poderia ter feito sem um par de variáveis ??e salvou várias linhas no processo.
public static String toHexString(byte[] bytes) {
char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[j*2] = hexArray[v/16];
hexChars[j*2 + 1] = hexArray[v%16];
}
return new String(hexChars);
}
O que eu gosto sobre isso é que é fácil de ver exatamente o que está fazendo (em vez de depender de alguma magia BigInteger conversão caixa preta) e você também está livre de ter que se preocupar com casos de canto, como líder de zeros e outras coisas. Essa rotina leva cada 4 bits mordidela e transforma-lo em um char hex. E está usando uma pesquisa de tabela, por isso é provavelmente rápido. Ele provavelmente poderia ser mais rápido se você substituir v / 16 e v% 16 com mudanças bit a bit e E do, mas eu sou muito preguiçoso para testá-lo agora.
Eu encontrei Integer.toHexString a ser um pouco lento. Se você estiver convertendo muitos bytes, você pode querer considerar a construção de uma matriz de strings contendo "00" .. "FF" e usar o número inteiro como o índice. I.
hexString.append(hexArray[0xFF & messageDigest[i]]);
Esta é mais rápido e garante o comprimento correto. Apenas requer a matriz de strings:
String[] hexArray = {
"00","01","02","03","04","05","06","07","08","09","0A","0B","0C","0D","0E","0F",
"10","11","12","13","14","15","16","17","18","19","1A","1B","1C","1D","1E","1F",
"20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F",
"30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F",
"40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F",
"50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F",
"60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F",
"70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F",
"80","81","82","83","84","85","86","87","88","89","8A","8B","8C","8D","8E","8F",
"90","91","92","93","94","95","96","97","98","99","9A","9B","9C","9D","9E","9F",
"A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","AA","AB","AC","AD","AE","AF",
"B0","B1","B2","B3","B4","B5","B6","B7","B8","B9","BA","BB","BC","BD","BE","BF",
"C0","C1","C2","C3","C4","C5","C6","C7","C8","C9","CA","CB","CC","CD","CE","CF",
"D0","D1","D2","D3","D4","D5","D6","D7","D8","D9","DA","DB","DC","DD","DE","DF",
"E0","E1","E2","E3","E4","E5","E6","E7","E8","E9","EA","EB","EC","ED","EE","EF",
"F0","F1","F2","F3","F4","F5","F6","F7","F8","F9","FA","FB","FC","FD","FE","FF"};
Eu estive olhando para a mesma coisa ... algumas boas idéias aqui, mas eu corri algumas micro benchmarks. Eu encontrei o seguinte para ser o mais rápido (modificado de Ayman de acima e cerca de 2x mais rápido, e cerca de 50% mais rápido do que Steve logo acima deste):
public static String hash(String text, String algorithm)
throws NoSuchAlgorithmException {
byte[] hash = MessageDigest.getInstance(algorithm).digest(text.getBytes());
return new BigInteger(1, hash).toString(16);
}
Editar: Oops - perdeu que este é essencialmente o mesmo que kgiannakakis de e assim pode retirar uma das principais 0. Ainda assim, modificar isso para o seguinte, ele ainda é o mais rápido:
public static String hash(String text, String algorithm)
throws NoSuchAlgorithmException {
byte[] hash = MessageDigest.getInstance(algorithm).digest(text.getBytes());
BigInteger bi = new BigInteger(1, hash);
String result = bi.toString(16);
if (result.length() % 2 != 0) {
return "0" + result;
}
return result;
}
static String toHex(byte[] digest) {
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%1$02X", b));
}
return sb.toString();
}
String result = String.format("%0" + messageDigest.length + "s", hexString.toString())
Essa é a solução mais curto dado o que você já tem. Se você poderia converter a matriz de bytes para um valor numérico, String.format
pode convertê-lo para uma string hex ao mesmo tempo.
Esta solução é uma escola pouco mais velhos, e deve ser eficiente de memória.
public static String toHexString(byte bytes[]) {
if (bytes == null) {
return null;
}
StringBuffer sb = new StringBuffer();
for (int iter = 0; iter < bytes.length; iter++) {
byte high = (byte) ( (bytes[iter] & 0xf0) >> 4);
byte low = (byte) (bytes[iter] & 0x0f);
sb.append(nibble2char(high));
sb.append(nibble2char(low));
}
return sb.toString();
}
private static char nibble2char(byte b) {
byte nibble = (byte) (b & 0x0f);
if (nibble < 10) {
return (char) ('0' + nibble);
}
return (char) ('a' + nibble - 10);
}
Outra opção
public static String toHexString(byte[]bytes) {
StringBuilder sb = new StringBuilder(bytes.length*2);
for(byte b: bytes)
sb.append(Integer.toHexString(b+0x800).substring(1));
return sb.toString();
}
Goiaba torna bastante simples também:
BaseEncoding.base16().encode( bytes );
É uma boa alternativa quando Apache Commons não está disponível. Ele também tem alguns bons controles de saída como:
byte[] bytes = new byte[] { 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
BaseEncoding.base16().lowerCase().withSeparator( ":", 2 ).encode( bytes );
// "0a:0b:0c:0d:0e:0f"
Gostaria de usar algo como isto para comprimento fixo, como hashes:
md5sum = String.format("%032x", new BigInteger(1, md.digest()));
O 0
na máscara faz o preenchimento ...
A fim de manter zeros à esquerda, aqui está uma pequena variação sobre o que tem Paul sugeriu (eg hash MD5):
public static String MD5hash(String text) throws NoSuchAlgorithmException {
byte[] hash = MessageDigest.getInstance("MD5").digest(text.getBytes());
return String.format("%032x",new BigInteger(1, hash));
}
Oops, isso parece mais pobres do que o que é Ayman proposto, desculpe por isso
static String toHex(byte[] digest) {
String digits = "0123456789abcdef";
StringBuilder sb = new StringBuilder(digest.length * 2);
for (byte b : digest) {
int bi = b & 0xff;
sb.append(digits.charAt(bi >> 4));
sb.append(digits.charAt(bi & 0xf));
}
return sb.toString();
}
Parece funções concat e de anexação pode ser muito lento. O seguinte foi muito mais rápido para mim (que o meu post anterior). Alterando a uma matriz de char na construção da saída era o factor-chave para acelerá-lo. Eu não tenho comparação com Hex.encodeHex sugerido por Brandon Durette.
public static String toHexString(byte[] bytes) {
char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[10000000];
int c = 0;
int v;
for ( j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[c] = hexArray[v/16];
c++;
hexChars[c] = hexArray[v%16];
c++;
}
return new String(hexChars, 0, c); }
Isto é o que eu estou usando para hashes MD5:
public static String getMD5(String filename)
throws NoSuchAlgorithmException, IOException {
MessageDigest messageDigest =
java.security.MessageDigest.getInstance("MD5");
InputStream in = new FileInputStream(filename);
byte [] buffer = new byte[8192];
int len = in.read(buffer, 0, buffer.length);
while (len > 0) {
messageDigest.update(buffer, 0, len);
len = in.read(buffer, 0, buffer.length);
}
in.close();
return new BigInteger(1, messageDigest.digest()).toString(16);
}
EDIT: Eu testei e tenho notado que, com esta zeros também à direita são cortadas. Mas isso só pode acontecer no início, para que você possa comparar com o comprimento esperado e almofada em conformidade.
Você pode obtê-lo escrevendo menos sem bibliotecas externas:
String hex = (new HexBinaryAdapter()).marshal(md5.digest(YOUR_STRING.getBytes()))
Esta solução não requer tabelas de pesquisa ou -masking, de mudança de bits, ou bibliotecas externas, e é quase tão curto como eu pode obter:
byte[] digest = new byte[16];
Formatter fmt = new Formatter();
for (byte b : digest) {
fmt.format("%02X", b);
}
fmt.toString()
byte messageDigest[] = algorithm.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < messageDigest.length; i++) {
String hexByte = Integer.toHexString(0xFF & messageDigest[i]);
int numDigits = 2 - hexByte.length();
while (numDigits-- > 0) {
hexString.append('0');
}
hexString.append(hexByte);
}
IMHO todas as soluções acima que fornecem trechos para remover os zeros à esquerda estão errados.
byte messageDigest[] = algorithm.digest();
for (int i = 0; i < messageDigest.length; i++) {
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
}
De acordo com este fragmento, 8 bits são tomadas a partir da matriz de bytes numa iteração, convertido em um número inteiro (já que a função Integer.toHexString leva int como argumento) e, em seguida, esse inteiro é convertido para o hash correspondendo valor. Assim, por exemplo, se você tiver 00000001 00000001 em binário, de acordo com o código, a variável hexstring teria 0x11 como o valor hexadecimal enquanto valor correto deve ser 0x0101. Assim, enquanto o cálculo MD5 podemos obter hashes de comprimento <32 bytes (por causa da falta zeros), que não podem satisfazer o criptograficamente propriedades únicas que hash MD5 faz.
A solução para o problema é substituir o trecho de código acima pelo seguinte trecho:
byte messageDigest[] = algorithm.digest();
for (int i = 0; i < messageDigest.length; i++) {
int temp=0xFF & messageDigest[i];
String s=Integer.toHexString(temp);
if(temp<=0x0F){
s="0"+s;
}
hexString.append(s);
}
Isto vai dar cadeia longa de duas caractere de um byte.
public String toString(byte b){
final char[] Hex = new String("0123456789ABCDEF").toCharArray();
return "0x"+ Hex[(b & 0xF0) >> 4]+ Hex[(b & 0x0F)];
}
E como você pode converter de volta novamente a partir ascii a matriz de bytes?
i seguido seguinte código para converter ascii dada pelo Jemenake.
public static String toHexString(byte[] bytes) {
char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[j*2] = hexArray[v/16];
hexChars[j*2 + 1] = hexArray[v%16];
}
return new String(hexChars);
}
minha variante
StringBuilder builder = new StringBuilder();
for (byte b : bytes)
{
builder.append(Character.forDigit(b/16, 16));
builder.append(Character.forDigit(b % 16, 16));
}
System.out.println(builder.toString());
ele funciona para mim.
É uma solução com defeito? (Android java)
// Create MD5 Hash
MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
digest.update(s.getBytes());
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
String stringMD5 = bigInt.toString(16);
// Fill to 32 chars
stringMD5 = String.format("%32s", stringMD5).replace(' ', '0');
return stringMD5;
Então, basicamente, ele substitui os espaços com 0.
Estou surpreso que ninguém veio acima com a seguinte solução:
StringWriter sw = new StringWriter();
com.sun.corba.se.impl.orbutil.HexOutputStream hex = new com.sun.corba.se.impl.orbutil.HexOutputStream(sw);
hex.write(byteArray);
System.out.println(sw.toString());
Ou você pode fazer isso:
byte[] digest = algorithm.digest();
StringBuilder byteContet = new StringBuilder();
for(byte b: digest){
byteContent = String.format("%02x",b);
byteContent.append(byteContent);
}
sua curta, simples e, basicamente, apenas uma mudança de formato.
Esta é também equivalente, mas mais concisa usando Apache util HexBin onde o código reduz a
HexBin.encode(messageDigest).toLowerCase();