Como converter matriz uint8 em string codificada base64?

90

Eu tenho uma comunicação webSocket, eu recebo uma string codificada em base64, converto em uint8 e trabalho nisso, mas agora eu preciso enviar de volta, eu tenho o array uint8 e preciso convertê-lo em string base64, então posso enviar. Como posso fazer essa conversão?

Caio Keto
fonte
A questão "ArrayBuffer para sequência codificada em base64" contém uma solução melhor que trata todos os caracteres. stackoverflow.com/questions/9267899/…
Steve Hanov

Respostas:

16

Todas as soluções já propostas apresentam problemas graves. Algumas soluções não funcionam em matrizes grandes, algumas fornecem saída incorreta, algumas geram um erro na chamada de btoa se uma string intermediária contiver caracteres multibyte, algumas consomem mais memória do que o necessário.

Então, implementei uma função de conversão direta que funciona independentemente da entrada. Ele converte cerca de 5 milhões de bytes por segundo na minha máquina.

https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727

Egor Nepomnyaschih
fonte
Ter base64abc como um array de strings é mais rápido do que apenas torná-lo um string? "ABCDEFG..."?
Garr Godfrey
163

Se seus dados podem conter sequências de bytes múltiplos (não uma sequência ASCII simples) e seu navegador tem TextDecoder , então você deve usar isso para decodificar seus dados (especifique a codificação necessária para o TextDecoder):

var u8 = new Uint8Array([65, 66, 67, 68]);
var decoder = new TextDecoder('utf8');
var b64encoded = btoa(decoder.decode(u8));

Se você precisa oferecer suporte a navegadores que não têm TextDecoder (atualmente apenas IE e Edge), a melhor opção é usar um polyfill TextDecoder .

Se seus dados contiverem ASCII simples (não Unicode / UTF-8 multibyte), há uma alternativa simples de uso String.fromCharCodeque deve ter suporte universalmente:

var ascii = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(String.fromCharCode.apply(null, ascii));

E para decodificar a string base64 de volta para um Uint8Array:

var u8_2 = new Uint8Array(atob(b64encoded).split("").map(function(c) {
    return c.charCodeAt(0); }));

Se você tiver buffers de array muito grandes, a aplicação pode falhar e você pode precisar dividir o buffer (com base no postado por @RohitSengar). Novamente, observe que isso está correto apenas se o seu buffer contiver apenas caracteres ASCII não multibyte:

function Uint8ToString(u8a){
  var CHUNK_SZ = 0x8000;
  var c = [];
  for (var i=0; i < u8a.length; i+=CHUNK_SZ) {
    c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ)));
  }
  return c.join("");
}
// Usage
var u8 = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(Uint8ToString(u8));
Kanaka
fonte
4
Isso está funcionando para mim no Firefox, mas o Chrome engasga com "Uncaught RangeError: Tamanho máximo da pilha de chamadas excedido" (fazendo o btoa).
Michael Paulukonis
3
@MichaelPaulukonis, meu palpite é que, na verdade, é o String.fromCharCode.apply que está fazendo com que o tamanho da pilha seja excedido. Se você tiver um Uint8Array muito grande, provavelmente precisará construir iterativamente a string em vez de usar o apply para fazer isso. A chamada de apply () está passando cada elemento de seu array como um parâmetro para fromCharCode, então se o array tem 128.000 bytes, então você tentaria fazer uma chamada de função com 128.000 parâmetros que provavelmente explodiriam a pilha.
Kanaka de
4
Obrigado. Tudo que eu precisava erabtoa(String.fromCharCode.apply(null, myArray))
Glen Little
29
Isso não funciona se a matriz de bytes não for Unicode válida.
Melab de
11
Não há caracteres multibyte em uma string base64 ou em Uint8Array. TextDecoderé absolutamente errado usar aqui, porque se o seu Uint8Arraytiver bytes no intervalo 128..255, o decodificador de texto irá convertê-los erroneamente em caracteres Unicode, o que quebrará o conversor base64.
riv
26

Solução e teste muito simples para JavaScript!

ToBase64 = function (u8) {
    return btoa(String.fromCharCode.apply(null, u8));
}

FromBase64 = function (str) {
    return atob(str).split('').map(function (c) { return c.charCodeAt(0); });
}

var u8 = new Uint8Array(256);
for (var i = 0; i < 256; i++)
    u8[i] = i;

var b64 = ToBase64(u8);
console.debug(b64);
console.debug(FromBase64(b64));
impactro
fonte
4
Solução mais limpa!
realappie
Solução perfeita
Haris ur Rehman
2
falha em grandes dados (como imagens) comRangeError: Maximum call stack size exceeded
Maxim Khokhryakov
21

Se você estiver usando Node.js, você pode usar este código para converter Uint8Array em base64

var b64 = Buffer.from(u8).toString('base64');
Fiach Reid
fonte
4
Esta é uma resposta melhor do que as funções roladas à mão acima em termos de desempenho.
Ben Liyanage
2
Impressionante! Obrigado. Melhor resposta de todas
Alan
18
function Uint8ToBase64(u8Arr){
  var CHUNK_SIZE = 0x8000; //arbitrary number
  var index = 0;
  var length = u8Arr.length;
  var result = '';
  var slice;
  while (index < length) {
    slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length)); 
    result += String.fromCharCode.apply(null, slice);
    index += CHUNK_SIZE;
  }
  return btoa(result);
}

Você pode usar esta função se tiver um Uint8Array muito grande. Isso é para Javascript, pode ser útil no caso de FileReader readAsArrayBuffer.

Rohit Singh Sengar
fonte
2
Curiosamente, no Chrome eu cronometrei isso em um buffer de 300kb + e descobri que fazer isso em pedaços como se você fosse um pouco mais lento do que fazer byte por byte. Isso me surpreendeu.
Matt
@Matt interessante. É possível que, nesse ínterim, o Chrome já detecte essa conversão e tenha uma otimização específica para ela, e a fragmentação dos dados pode reduzir sua eficiência.
Kanaka,
2
Isso não é seguro, é? Se o limite do meu trecho atravessa um caractere codificado em UTF8 de vários bytes, então fromCharCode () não seria capaz de criar caracteres sensíveis a partir dos bytes em ambos os lados do limite, não é?
Jens
2
Os String.fromCharCode.apply()métodos @Jens não podem reproduzir UTF-8: caracteres UTF-8 podem variar em comprimento de um byte a quatro bytes, mas String.fromCharCode.apply()examina um UInt8Array em segmentos de UInt8, portanto, assume erroneamente que cada caractere tem exatamente um byte de comprimento e é independente do vizinho uns. Se todos os caracteres codificados na entrada UInt8Array estiverem no intervalo ASCII (byte único), funcionará por acaso, mas não pode reproduzir UTF-8 completo. Você precisa de TextDecoder ou um algoritmo semelhante para isso.
Jamie Birch
1
@Jens que caracteres codificados em UTF8 multibyte em uma matriz de dados binários? Não estamos lidando com strings unicode aqui, mas com dados binários arbitrários, que NÃO devem ser tratados como pontos de código utf-8.
riv
0

Aqui está uma função JS para isso:

Esta função é necessária porque o Chrome não aceita uma string codificada em base64 como valor para applicationServerKey em pushManager.subscribe ainda https://bugs.chromium.org/p/chromium/issues/detail?id=802280

function urlBase64ToUint8Array(base64String) {
  var padding = '='.repeat((4 - base64String.length % 4) % 4);
  var base64 = (base64String + padding)
    .replace(/\-/g, '+')
    .replace(/_/g, '/');

  var rawData = window.atob(base64);
  var outputArray = new Uint8Array(rawData.length);

  for (var i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}
lucss
fonte
3
Isso converte base64 em Uint8Array. Mas a questão é como converter Uint8Array em base64
Barry Michael Doyle
0

JS puro - sem meio-passo de string (sem btoa)

Na solução abaixo, omito a conversão para string. IDEA é o seguinte:

  • junte 3 bytes (3 elementos da matriz) e você terá 24 bits
  • dividir 24 bits em quatro números de 6 bits (que assumem valores de 0 a 63)
  • use esses números como índice no alfabeto base64
  • caso de canto: quando a matriz de bytes de entrada, o comprimento não é dividido por 3, então adicione =ou ==ao resultado

A solução abaixo funciona em blocos de 3 bytes, portanto é boa para grandes arrays. Uma solução semelhante para converter base64 em matriz binária (sem atob) está AQUI

Kamil Kiełczewski
fonte
Eu gosto da compactação, mas converter em strings que representam o número binário e depois voltar é muito mais lento do que a solução aceita.
Garr Godfrey
0

Use o seguinte para converter a matriz uint8 em uma string codificada em base64

function arrayBufferToBase64(buffer) {
            var binary = '';
            var bytes = [].slice.call(new Uint8Array(buffer));
            bytes.forEach((b) => binary += String.fromCharCode(b));
            return window.btoa(binary);
        };
KARTHIKEYAN.A
fonte
-3

Se tudo o que você deseja é uma implementação JS de um codificador base64, para poder enviar dados de volta, experimente a btoafunção.

b64enc = btoa(uint);

Algumas notas rápidas sobre btoa - não é padrão, então os navegadores não são forçados a suportá-lo. No entanto, a maioria dos navegadores sim. Os grandes, pelo menos.atobé a conversão oposta.

Se você precisar de uma implementação diferente ou encontrar um caso extremo em que o navegador não tenha ideia do que você está falando, procurar um codificador base64 para JS não seria muito difícil.

Acho que há 3 deles circulando no site da minha empresa, por algum motivo ...

Norguard
fonte
Obrigado, eu não experimentei isso antes.
Caio Keto
10
Algumas notas. btoa e atob são, na verdade, parte do processo de padronização do HTML5 e a maioria dos navegadores os suportam basicamente da mesma maneira. Em segundo lugar, btoa e atob funcionam apenas com strings. Executar btoa no Uint8Array primeiro converterá o buffer em uma string usando toString (). Isso resulta na string "[object Uint8Array]". Provavelmente não é isso que se pretende.
Kanaka,
1
@CaioKeto, talvez você queira alterar a resposta selecionada. Esta resposta não está correta.
Kanaka,
-4

npm install google-closure-library --save

require("google-closure-library");
goog.require('goog.crypt.base64');

var result =goog.crypt.base64.encodeByteArray(Uint8Array.of(1,83,27,99,102,66));
console.log(result);

$node index.jsescreveria AVMbY2Y = no console.

mancini0
fonte
1
É engraçado que uma -veresposta votada seja aceita ao invés de uma altamente +ve.
Vishnudev de