Como criptografar String em Java


150

O que eu preciso é criptografar a string que será exibida no código de barras 2D (PDF-417). Assim, quando alguém tiver uma idéia para digitalizar, nada ficará legível.

Outros requerimentos:

  • não deve ser complicado
  • não deve consistir em RSA, infraestrutura PKI, pares de chaves etc.

Deve ser simples o suficiente para se livrar das pessoas bisbilhoteiras e fácil de descriptografar para outras empresas interessadas em obter esses dados. Eles nos chamam, dizemos o padrão ou damos a eles uma chave simples que pode ser usada para descriptografia.

Provavelmente essas empresas poderiam usar tecnologias diferentes, por isso seria bom seguir algum padrão que não esteja vinculado a alguma plataforma ou tecnologia especial.

O que você sugere? Existe alguma classe Java fazendo encrypt()e decrypt()sem muita complicação na obtenção de altos padrões de segurança?



Atenção . Muitas das respostas abaixo mostram um método ou outro para executar qualquer tipo de criptografia em Java. As respostas podem não refletir boas práticas criptográficas e podem não ser bem revisadas; não existe segurança de copiar / colar . As respostas devem pelo menos levar em consideração a conversão de string. A questão real com o código de barras 2D incluído é muito ampla e deve exigir uma solução específica para o cliente.
Maarten Bodewes

Respostas:


156

Esta é a primeira página que aparece através do Google e as vulnerabilidades de segurança em todas as implementações me fazem estremecer, então eu estou postando isso para adicionar informações sobre criptografia para outras pessoas, pois faz 7 anos da publicação original. Eu tenho um mestrado em engenharia da computação e passei muito tempo estudando e aprendendo criptografia, então estou jogando meus dois centavos para tornar a internet um lugar mais seguro.

Além disso, observe que muita implementação pode ser segura para uma determinada situação, mas por que usá-las e, potencialmente, acidentalmente cometer um erro? Use as ferramentas mais fortes disponíveis, a menos que você tenha um motivo específico para não fazê-lo. No geral, eu recomendo usar uma biblioteca e ficar longe dos detalhes básicos, se puder.

ATUALIZAÇÃO 4/5/18: reescrevi algumas partes para torná-las mais simples de entender e alterei a biblioteca recomendada do Jasypt para a nova biblioteca do Google Tink , recomendo remover completamente o Jasypt de uma configuração existente.

Prefácio

A seguir, descreverei o básico da criptografia simétrica segura e apontarei os erros comuns que vejo online quando as pessoas implementam criptografia por conta própria com a biblioteca Java padrão. Se você quiser pular todos os detalhes, vá para a nova biblioteca do Google. Importe isso para o seu projeto e use o modo AES-GCM para todas as suas criptografias e você estará seguro.

Agora, se você quiser aprender os detalhes básicos sobre como criptografar em java, continue lendo :)

Cifras de bloco

Primeiro, você precisa escolher uma chave simétrica Block Cipher. Uma cifra de bloco é uma função / programa de computador usada para criar pseudo-aleatoriedade. A pseudo-aleatoriedade é uma aleatoriedade falsa que nenhum computador que não seja um computador quântico seria capaz de distinguir a diferença entre ele e a aleatoriedade real. A Cifra de Bloco é como o bloco de construção da criptografia e, quando usado com diferentes modos ou esquemas, podemos criar criptografias.

Agora, sobre os algoritmos de codificação de bloco disponíveis hoje, certifique-se de NUNCA , repito NUNCA use DES , diria mesmo NUNCA use 3DES . O único Block Cipher que até o lançamento da NSA de Snowden foi capaz de verificar estar realmente o mais próximo possível do pseudo-aleatório possível é o AES 256 . Também existe AES 128; a diferença é que o AES 256 funciona em blocos de 256 bits, enquanto o AES 128 funciona em 128 blocos. Em suma, o AES 128 é considerado seguro, embora algumas fraquezas tenham sido descobertas, mas 256 é o mais sólido possível.

Curiosidade O DES foi quebrado pela NSA quando foi inicialmente fundado e, na verdade, manteve um segredo por alguns anos. Embora algumas pessoas ainda afirmem que o 3DES é seguro, existem muitos trabalhos de pesquisa que encontraram e analisaram pontos fracos no 3DES .

Modos de criptografia

A criptografia é criada quando você pega uma cifra de bloco e usa um esquema específico para que a aleatoriedade seja combinada com uma chave para criar algo que seja reversível, desde que você conheça a chave. Isso é chamado de modo de criptografia.

Aqui está um exemplo de um modo de criptografia e o modo mais simples conhecido como BCE, para que você possa entender visualmente o que está acontecendo:

Modo BCE

Os modos de criptografia que você verá mais comumente online são os seguintes:

CTR do BCE, CBC, GCM

Existem outros modos fora dos listados e os pesquisadores estão sempre trabalhando em novos modos para melhorar os problemas existentes.

Agora vamos às implementações e ao que é seguro. NUNCA use o BCE, isso é ruim para ocultar dados repetidos, como mostra o famoso pinguim Linux .Exemplo de Linux Penguin

Ao implementar em Java, observe que, se você usar o seguinte código, o modo ECB será definido por padrão:

Cipher cipher = Cipher.getInstance("AES");

... PERIGO, ESTA É UMA VULNERABILIDADE! e, infelizmente, isso é visto em todo o StackOverflow e on-line em tutoriais e exemplos.

Nonces e IVs

Em resposta à questão encontrada no modo BCE, também foram criados nomes conhecidos como IVs. A idéia é que geremos uma nova variável aleatória e a anexemos a toda criptografia, para que, quando você criptografar duas mensagens iguais, elas saiam diferentes. A beleza por trás disso é que um IV ou nonce é de conhecimento público. Isso significa que um invasor pode ter acesso a isso, mas desde que não possua sua chave, não poderá fazer nada com esse conhecimento.

Problemas comuns que verei é que as pessoas definirão o IV como um valor estático, como no mesmo valor fixo em seu código. e aqui está a armadilha para os IVs no momento em que você repete um, você realmente compromete toda a segurança de sua criptografia.

Gerando um IV aleatório

SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

Nota: O SHA1 está quebrado, mas não consegui descobrir como implementar o SHA256 nesse caso de uso corretamente; portanto, se alguém quiser fazer uma rachadura e atualizá-lo, seria incrível! Além disso, os ataques SHA1 ainda não são convencionais, pois pode levar alguns anos em um grande cluster para quebrar. Confira os detalhes aqui.

Implementação de CTR

Não é necessário preenchimento para o modo CTR.

 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

Implementação de CBC

Se você optar por implementar o Modo CBC, faça-o com PKCS7Padding da seguinte maneira:

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

Vulnerabilidade de CBC e CTR e por que você deve usar o GCM

Embora alguns outros modos, como CBC e CTR, sejam seguros, eles enfrentam o problema em que um invasor pode inverter os dados criptografados, alterando seu valor quando descriptografado. Então, digamos que você criptografa uma mensagem bancária imaginária "Sell 100", sua mensagem criptografada se parece com "eu23ng", o invasor muda um pouco para "eu53ng" e, de repente, quando descriptografa sua mensagem, lê-se como "Sell 900".

Para evitar isso, a maioria da internet usa o GCM, e toda vez que você vê HTTPS, provavelmente está usando o GCM. O GCM assina a mensagem criptografada com um hash e verifica se a mensagem não foi alterada usando esta assinatura.

Eu evitaria implementar o GCM devido à sua complexidade. É melhor usar a nova biblioteca do Google, Tink, porque aqui novamente, se você repetir acidentalmente um IV, estará comprometendo a chave no caso do GCM, que é a falha definitiva de segurança. Novos pesquisadores estão trabalhando para os modos de criptografia resistente à repetição IV, onde, mesmo se você repetir o IV, a chave não está em perigo, mas isso ainda está por vir.

Agora, se você deseja implementar o GCM, aqui está um link para uma boa implementação do GCM . No entanto, não posso garantir a segurança ou se está devidamente implementada, mas reduz a base. Observe também que no GCM não há preenchimento.

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

Chaves vs senhas

Outra observação muito importante é que, quando se trata de criptografia, uma chave e uma senha não são as mesmas coisas. Uma chave na criptografia precisa ter uma certa quantidade de entropia e aleatoriedade para ser considerada segura. É por isso que você precisa se certificar de usar as bibliotecas criptográficas adequadas para gerar a chave para você.

Então você realmente tem duas implementações que você pode fazer aqui, a primeira é usar o código encontrado neste encadeamento StackOverflow para geração de chave aleatória . Esta solução usa um gerador de números aleatórios seguro para criar uma chave do zero que você pode usar.

A outra opção menos segura é usar a entrada do usuário, como uma senha. O problema, como discutimos, é que a senha não tem entropia suficiente, portanto, teríamos que usar o PBKDF2 , um algoritmo que pega a senha e a fortalece. Aqui está uma implementação StackOverflow que eu gostei . No entanto, a biblioteca do Google Tink tem tudo isso embutido e você deve tirar proveito disso.

Desenvolvedores Android

Um ponto importante a ser destacado aqui é saber que seu código Android é reversível e, na maioria dos casos, a maioria dos códigos Java também. Isso significa que se você armazenar a senha em texto sem formatação no seu código. Um hacker pode recuperá-lo facilmente. Normalmente, para esse tipo de criptografia, você deseja usar a criptografia assimétrica e assim por diante. Isso está fora do escopo deste post, por isso vou evitar mergulhar nele.

Uma leitura interessante de 2013 : salienta que 88% das implementações de criptografia no Android foram feitas de maneira inadequada.

Pensamentos finais

Mais uma vez, sugiro evitar a implementação da biblioteca java para criptografia diretamente e usar o Google Tink . Isso poupará a dor de cabeça, pois eles realmente fizeram um bom trabalho ao implementar todos os algoritmos corretamente. E mesmo assim, verifique as questões levantadas no github Tink, as vulnerabilidades aparecem aqui e ali.

Se você tiver alguma dúvida ou feedback, sinta-se à vontade para comentar! A segurança está sempre mudando e você precisa fazer o possível para acompanhá-la :)


15
Esta é a coisa mais limpa que eu já vi.
Seraf

1
@SabirKhan Poderia ser um motivo de preocupação, mas os algoritmos principais ainda não foram quebrados, então eu não ficaria muito preocupado com isso. No caso em que você não confia, consulte também github.com/google/keyczar , que foi desenvolvido pela equipe de segurança do googles.
Konstantino Sparakis

1
@ KonstantinoSparakis: Se eu não interpretou mal a documentação do BasicTextEncryptor e StrongTextEncryptor do Jasypt, essas classes usam DES e 3DES para criptografar, que é exatamente o que você pede aos leitores para não usar. Na IMO, você deve substituir os exemplos de código fornecidos por um que faça uso do StandardPBEStringEncryptor do Jasypt e defina manualmente um algoritmo AES a ser usado.
Xpages-noob 23/03/19

1
@ xpages-noob Atualizei a postagem. Na verdade, eu encontrei o Google Tink, que é a mais nova biblioteca suportada de criptografia, então você deve conferir!
Konstantino Sparakis

2
O tamanho do bloco AES é de 128 bits. No AES 256, o tamanho da chave é de 256 bits. Da mesma forma, AES 192 e AES 128. Além disso, como Java 8, getInstanceStrong()método de Cipheré preferível ao longo SHA1PRNG
Saptarshi Basu

110

Eu recomendaria usar algum código simétrico padrão amplamente disponível como DES , 3DES ou AES . Embora esse não seja o algoritmo mais seguro, existem muitas implementações e você só precisa fornecer a chave para qualquer pessoa que decodifique as informações no código de barras. javax.crypto.Cipher é com o que você deseja trabalhar aqui.

Vamos supor que os bytes a serem criptografados estejam em

byte[] input;

Em seguida, você precisará dos bytes do vetor de chave e inicialização

byte[] keyBytes;
byte[] ivBytes;

Agora você pode inicializar a cifra para o algoritmo selecionado:

// wrap key data in Key/IV specs to pass to cipher
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

A criptografia seria assim:

cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted= new byte[cipher.getOutputSize(input.length)];
int enc_len = cipher.update(input, 0, input.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);

E descriptografia como esta:

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(encrypted, 0, enc_len, decrypted, 0);
dec_len += cipher.doFinal(decrypted, dec_len);

9
Posso sugerir que você atualize este exemplo para referenciar o DESedealgoritmo? Como essa é uma pergunta popular (e resposta), seria uma pena incentivar as pessoas a usar o DES, uma vez que a cifra é tão fraca para os padrões atuais.
Duncan Jones

algo errado com javax.crypto.BadPaddingException: dado o bloco final não foi preenchido corretamente durante a recriação
curiosidade

2
@Duncan De fato, o DES é fraco, mas suponho que o AES seria preferível ao DESede (também conhecido como TipleDES): http://security.stackexchange.com/a/26181/69785
Piovezan

2
Isto deve ser atualizada para ter AES / GCM / NoPadding, DES é vulnerável a ataques bruteforce, tripleDES não é recomendado tanto
Konstantino Sparakis

1
A resposta de Konstantino Sparakis abaixo é MUITO melhor do que esta.
Steve

22

Aviso

Não use isso como algum tipo de medida de segurança.

O mecanismo de criptografia nesta postagem é um bloco de uso único, o que significa que a chave secreta pode ser facilmente recuperada por um invasor usando duas mensagens criptografadas. XOR 2 mensagens criptografadas e você obtém a chave. Que simples!

Apontado por Moussa


Estou usando o Base64Encoder / Decoder da Sun, que pode ser encontrado no JRE da Sun, para evitar outro JAR na lib. Isso é perigoso do ponto de usar o OpenJDK ou o JRE de outra pessoa. Além disso, existe outro motivo pelo qual eu deveria considerar o uso do Apache commons lib com o Encoder / Decoder?

public class EncryptUtils {
    public static final String DEFAULT_ENCODING = "UTF-8"; 
    static BASE64Encoder enc = new BASE64Encoder();
    static BASE64Decoder dec = new BASE64Decoder();

    public static String base64encode(String text) {
        try {
            return enc.encode(text.getBytes(DEFAULT_ENCODING));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }//base64encode

    public static String base64decode(String text) {
        try {
            return new String(dec.decodeBuffer(text), DEFAULT_ENCODING);
        } catch (IOException e) {
            return null;
        }
    }//base64decode

    public static void main(String[] args) {
        String txt = "some text to be encrypted";
        String key = "key phrase used for XOR-ing";
        System.out.println(txt + " XOR-ed to: " + (txt = xorMessage(txt, key)));

        String encoded = base64encode(txt);       
        System.out.println(" is encoded to: " + encoded + " and that is decoding to: " + (txt = base64decode(encoded)));
        System.out.print("XOR-ing back to original: " + xorMessage(txt, key));
    }

    public static String xorMessage(String message, String key) {
        try {
            if (message == null || key == null) return null;

            char[] keys = key.toCharArray();
            char[] mesg = message.toCharArray();

            int ml = mesg.length;
            int kl = keys.length;
            char[] newmsg = new char[ml];

            for (int i = 0; i < ml; i++) {
                newmsg[i] = (char)(mesg[i] ^ keys[i % kl]);
            }//for i

            return new String(newmsg);
        } catch (Exception e) {
            return null;
        }
    }//xorMessage
}//class

1
Também usei essa proposta de solução via sun.misc.BASE64Encoder, mas ao usar cadeias bastante grandes para codificar, o codificador retornou cadeias em pedaços (76 caracteres cada). Eu então mudei para o Apache Commons Codec Base64, que oferece métodos de codificação sem fragmentos!
basZero

78
O mecanismo de criptografia que você descreveu é MUITO PERIGOSO se usado mais de uma vez. essa é a razão pela qual é chamado de almofada única. A chave secreta pode ser facilmente recuperada por um invasor usando 2 mensagens criptografadas. xor 2 mensagens criptografadas e você obtém a chave. Que simples!
Xtrem 11/10/12

3
Sua idéia não é ser pesada, apenas para evitar que as pessoas tentem ler o que está escrito nos códigos de barras 2D-PDF 417. E de qualquer maneira, há apenas alguns índices não crucial para qualquer um ...
ante.sabo

2
ESTÁ BEM. Apenas preocupado que alguém use isso como um mecanismo de criptografia.
Xtrem 12/10/12

Para criptografia, o codificador (por exemplo, BASE64Encoder) pode ser evitado para ter ataques de força bruta.
Jagrut Dalwadi

13

obrigado ive fez esta classe usando o seu código talvez alguém ache userfull

criptografador de objetos

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class ObjectCrypter {

private Cipher deCipher;
private Cipher enCipher;
private SecretKeySpec key;
private IvParameterSpec ivSpec;


public ObjectCrypter(byte[] keyBytes,   byte[] ivBytes) {
    // wrap key data in Key/IV specs to pass to cipher


     ivSpec = new IvParameterSpec(ivBytes);
    // create the cipher with the algorithm you choose
    // see javadoc for Cipher class for more info, e.g.
    try {
         DESKeySpec dkey = new  DESKeySpec(keyBytes);
          key = new SecretKeySpec(dkey.getKey(), "DES");
         deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
         enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
public byte[] encrypt(Object obj) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, ShortBufferException, BadPaddingException {
    byte[] input = convertToByteArray(obj);
    enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    return enCipher.doFinal(input);




//  cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
//  byte[] encypted = new byte[cipher.getOutputSize(input.length)];
//  int enc_len = cipher.update(input, 0, input.length, encypted, 0);
//  enc_len += cipher.doFinal(encypted, enc_len);
//  return encypted;


}
public Object decrypt( byte[]  encrypted) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException {
    deCipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

    return convertFromByteArray(deCipher.doFinal(encrypted));

}



private Object convertFromByteArray(byte[] byteObject) throws IOException,
        ClassNotFoundException {
    ByteArrayInputStream bais;

    ObjectInputStream in;
    bais = new ByteArrayInputStream(byteObject);
    in = new ObjectInputStream(bais);
    Object o = in.readObject();
    in.close();
    return o;

}



private byte[] convertToByteArray(Object complexObject) throws IOException {
    ByteArrayOutputStream baos;

    ObjectOutputStream out;

    baos = new ByteArrayOutputStream();

    out = new ObjectOutputStream(baos);

    out.writeObject(complexObject);

    out.close();

    return baos.toByteArray();

}


}

postou uma pergunta relacionada aqui !
user2023507

Não deveria ser o caso que passar chaves diferentes durante a criptografia e descriptografia não retornasse o texto? Isso não parece estar acontecendo aqui. PS: Estou usando objetos diferentes desta classe para realizar este teste.
instanceOfObject

6

Atualização em 12-DEC-2019

Ao contrário de outros modos, como o CBC, o modo GCM não exige que o IV seja imprevisível. O único requisito é que o IV tenha que ser exclusivo para cada chamada com uma determinada chave. Se ele se repetir uma vez para uma determinada chave, a segurança pode ser comprometida. Uma maneira fácil de conseguir isso é usar um IV aleatório de um forte gerador de números pseudo-aleatórios, como mostrado abaixo.

O uso de uma sequência ou registro de data e hora como IV também é possível, mas pode não ser tão trivial quanto possível. Por exemplo, se o sistema não acompanhar corretamente as seqüências já usadas como IV em um armazenamento persistente, uma chamada poderá repetir um IV após a reinicialização do sistema. Da mesma forma, não há relógio perfeito. O relógio do computador reajusta etc.

Além disso, a chave deve ser girada após cada 2 ^ 32 invocações. Para mais detalhes sobre o requisito IV, consulte esta resposta e as recomendações do NIST .


Este é o código de criptografia e descriptografia que acabei de escrever no Java 8, considerando os seguintes pontos. Espero que alguém ache isso útil:

  1. Algoritmo de criptografia : O AES da cifra de bloco com chave de 256 bits é considerado suficientemente seguro. Para criptografar uma mensagem completa, um modo precisa ser selecionado. A criptografia autenticada (que fornece confidencialidade e integridade) é recomendada. GCM, CCM e EAX são os modos de criptografia autenticada mais comumente usados. O GCM é geralmente preferido e apresenta bom desempenho nas arquiteturas Intel, que fornecem instruções dedicadas para o GCM. Todos esses três modos são baseados em CTR (contador) e, portanto, não precisam de preenchimento. Como resultado, eles não são vulneráveis ​​a ataques relacionados ao preenchimento

  2. Um vetor de inicialização (IV) é necessário para o GCM. O IV não é um segredo. O único requisito é que deve ser aleatório ou imprevisível. Em Java, a SecuredRandomclasse pretende produzir números pseudo-aleatórios criptograficamente fortes. O algoritmo de geração de números pseudo-aleatórios pode ser especificado no getInstance()método No entanto, desde o Java 8, a maneira recomendada é usar o getInstanceStrong()método que usará o algoritmo mais forte configurado e fornecido peloProvider

  3. O NIST recomenda 96 bits IV para o GCM para promover a interoperabilidade, eficiência e simplicidade do design

  4. Para garantir segurança adicional, na implementação a seguir SecureRandomé re-propagada após a produção a cada 2 ^ 16 bytes de geração de bytes pseudo-aleatórios

  5. O destinatário precisa conhecer o IV para poder descriptografar o texto cifrado. Portanto, o IV precisa ser transferido junto com o texto cifrado. Algumas implementações enviam o IV como AD (Dados Associados), o que significa que a marca de autenticação será calculada no texto cifrado e no IV. No entanto, isso não é necessário. O IV pode ser simplesmente pendente com o texto cifrado, porque se o IV for alterado durante a transmissão devido a um ataque deliberado ou erro no sistema de rede / arquivo, a validação da tag de autenticação falhará de qualquer maneira

  6. As strings não devem ser usadas para conter a mensagem de texto não criptografado ou a tecla, pois as strings são imutáveis ​​e, portanto, não podemos eliminá-las após o uso. Essas seqüências de caracteres não claras permanecem na memória e podem aparecer em um despejo de pilha. Pelo mesmo motivo, o cliente que chama esses métodos de criptografia ou descriptografia deve limpar todas as variáveis ​​ou matrizes que contêm a mensagem ou a chave depois que elas não forem mais necessárias.

  7. Nenhum provedor é codificado no código seguindo as recomendações gerais

  8. Finalmente, para transmissão pela rede ou armazenamento, a chave ou o texto cifrado deve ser codificado usando a codificação Base64. Os detalhes do Base64 podem ser encontrados aqui . A abordagem Java 8 deve ser seguida

As matrizes de bytes podem ser limpas usando:

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

No entanto, no Java 8, não há uma maneira fácil de limpar SecretKeyspece, SecretKeycomo as implementações dessas duas interfaces não parecem ter implementado o método destroy()da interface Destroyable. No código a seguir, um método separado é gravado para limpar SecretKeySpece SecretKeyusar a reflexão.

A chave deve ser gerada usando uma das duas abordagens mencionadas abaixo.

Observe que as chaves são segredos, como senhas, mas, diferentemente das senhas destinadas ao uso humano, as chaves devem ser usadas por algoritmos criptográficos e, portanto, devem ser geradas usando apenas a maneira acima.

package com.sapbasu.javastudy;

import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Crypto {

  private static final int AUTH_TAG_SIZE = 128; // bits

  // NIST recommendation: "For IVs, it is recommended that implementations
  // restrict support to the length of 96 bits, to
  // promote interoperability, efficiency, and simplicity of design."
  private static final int IV_LEN = 12; // bytes

  // number of random number bytes generated before re-seeding
  private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16);

  private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";

  private static final List<Integer> ALLOWED_KEY_SIZES = Arrays
      .asList(new Integer[] {128, 192, 256}); // bits

  private static SecureRandom prng;

  // Used to keep track of random number bytes generated by PRNG
  // (for the purpose of re-seeding)
  private static int bytesGenerated = 0;

  public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception {

    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Length of message cannot be 0");
    }

    if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) {
      throw new IllegalArgumentException("Size of key must be 128, 192 or 256");
    }

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

    byte[] iv = getIV(IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec);
    byte[] messageCipher = cipher.doFinal(input);

    // Prepend the IV with the message cipher
    byte[] cipherText = new byte[messageCipher.length + IV_LEN];
    System.arraycopy(iv, 0, cipherText, 0, IV_LEN);
    System.arraycopy(messageCipher, 0, cipherText, IV_LEN,
        messageCipher.length);
    return cipherText;
  }

  public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception {
    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Input array cannot be empty");
    }

    byte[] iv = new byte[IV_LEN];
    System.arraycopy(input, 0, iv, 0, IV_LEN);

    byte[] messageCipher = new byte[input.length - IV_LEN];
    System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec);

    return cipher.doFinal(messageCipher);
  }

  public byte[] getIV(int bytesNum) {

    if (bytesNum < 1) throw new IllegalArgumentException(
        "Number of bytes must be greater than 0");

    byte[] iv = new byte[bytesNum];

    prng = Optional.ofNullable(prng).orElseGet(() -> {
      try {
        prng = SecureRandom.getInstanceStrong();
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Wrong algorithm name", e);
      }
      return prng;
    });

    if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) {
      prng.setSeed(prng.generateSeed(bytesNum));
      bytesGenerated = 0;
    }

    prng.nextBytes(iv);
    bytesGenerated = bytesGenerated + bytesNum;

    return iv;
  }

  private static void clearSecret(Destroyable key)
      throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    Field keyField = key.getClass().getDeclaredField("key");
    keyField.setAccessible(true);
    byte[] encodedKey = (byte[]) keyField.get(key);
    Arrays.fill(encodedKey, Byte.MIN_VALUE);
  }
}

A chave de criptografia pode ser gerada principalmente de duas maneiras:

  • Sem nenhuma senha

    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
  • Com senha

    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[32];
    random.nextBytes(salt);
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, 
       keyLength);
    SecretKeyFactory keyFactory = 
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);

Atualização baseada em comentários

Conforme apontado por @MaartenBodewes, minha resposta não lidou com nada Stringconforme exigido pela pergunta. Portanto, farei uma tentativa de preencher essa lacuna para o caso de alguém topar com essa resposta e deixar de pensar em como lidar String.

Conforme indicado anteriormente na resposta, manipular informações confidenciais em a Stringgeralmente não é uma boa ideia porque Stringé imutável e, portanto, não podemos eliminá-las após o uso. E como sabemos, mesmo quando um Stringnão tem uma referência forte, o coletor de lixo não corre imediatamente para removê-lo da pilha. Portanto, ele Stringpermanece na memória por uma janela desconhecida, mesmo que não seja acessível ao programa. O problema disso é que um despejo de pilha durante esse período revelaria as informações confidenciais. Portanto, é sempre melhor manipular todas as informações confidenciais em uma matriz de bytes ou em uma matriz de caracteres e, em seguida, preencher a matriz com 0s quando o objetivo for atendido.

No entanto, com todo esse conhecimento, se ainda acabarmos em uma situação em que as informações confidenciais a serem criptografadas estão em a String, primeiro precisamos convertê-las em uma matriz de bytes e invocar as funções encrypte decryptapresentadas acima. (A outra chave de entrada pode ser gerada usando o snippet de código fornecido acima).

A Stringpode ser convertido em bytes da seguinte maneira:

byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);

A partir do Java 8, Stringé armazenado internamente em heap com UTF-16codificação. No entanto, usamos UTF-8aqui, pois geralmente ocupa menos espaço do que UTF-16, especialmente para caracteres ASCII.

Da mesma forma, a matriz de bytes criptografados também pode ser convertida em uma String, como abaixo:

String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);

1
Por mais que eu queira aprovar esta resposta, pois ela parece aderir às práticas atuais de criptografia, não vejo nenhum manuseio de string, tornando-a mais como uma descrição de como usar o modo GCM. Como tal, ele não responde à pergunta .
Maarten Bodewes

1
@MaartenBodewes Muito obrigado por reservar um tempo para revisar e compartilhar seus comentários. Escrevi isso com o entendimento de que criptografar um Stringusando as funções criadas acima seria trivial. No entanto, depois de ler seu comentário, entendo que isso pode não ser óbvio. Certamente vou editar para adicionar esses detalhes.
Saptarshi Basu

5

Que tal agora:

private static byte[] xor(final byte[] input, final byte[] secret) {
    final byte[] output = new byte[input.length];
    if (secret.length == 0) {
        throw new IllegalArgumentException("empty security key");
    }
    int spos = 0;
    for (int pos = 0; pos < input.length; ++pos) {
        output[pos] = (byte) (input[pos] ^ secret[spos]);
        ++spos;
        if (spos >= secret.length) {
            spos = 0;
        }
    }
    return output;
}

Funciona bem para mim e é bastante compacto.


o que acontecerá se o parâmetro de entrada secret == null ou input == null? trabalhar com bytes e não com strings é bom, mas era irrelevante no meu caso .. a única coisa que importa é que isso deve ser legível e decodível com qualquer dispositivo, em qualquer caractere possível de codificação ...
ante.sabo

@ ante.sabo aparentemente, lançará um NPE. Essa é a única coisa a fazer com NULLs.
precisa saber é o seguinte

Enquanto input.length <= secret.lengthdetém e não secreté reutilizado, isso é seguro e chamado de one-time-pad. Nos casos input.length > secret.lengthdisso, é uma variante da cifra de Vigenère e considerada muito fraca.
trichner 10/01

5

Você pode usar o Jasypt

Com o Jasypt, criptografar e verificar uma senha pode ser tão simples quanto ...

StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);

Criptografia:

String myEncryptedText = textEncryptor.encrypt(myText);

Descriptografia:

String plainText = textEncryptor.decrypt(myEncryptedText);

Gradle:

compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'

Recursos:

O Jasypt fornece técnicas fáceis de criptografia unidirecional (resumo) e bidirecional.

API aberta para uso com qualquer provedor JCE, e não apenas o Java VM padrão. O Jasypt pode ser facilmente usado com fornecedores conhecidos como o Bouncy Castle. Saber mais.

Maior segurança para as senhas de seus usuários. Saber mais.

Suporte de criptografia binária. O Jasypt permite a digestão e criptografia de binários (matrizes de bytes). Criptografe seus objetos ou arquivos quando necessário (para serem enviados pela rede, por exemplo).

Suporte de criptografia numérica. Além de textos e binários, permite digerir e criptografar valores numéricos (BigInteger e BigDecimal, outros tipos numéricos são suportados ao criptografar para persistência do Hibernate). Saber mais.

Completamente seguro para threads.

Suporte para pool de criptografador / digestor, a fim de obter alto desempenho em sistemas com vários processadores / vários núcleos.

Inclui uma versão leve ("leve") da biblioteca para melhor capacidade de gerenciamento em ambientes com tamanho restrito, como plataformas móveis.

Fornece ferramentas de criptografia fáceis e sem configuração para usuários iniciantes em criptografia, e também ferramentas de criptografia padrão altamente configuráveis ​​para usuários avançados.

Integração opcional do Hibernate 3 e 4 para campos persistentes de suas entidades mapeadas de maneira criptografada. A criptografia de campos é definida nos arquivos de mapeamento do Hibernate e permanece transparente para o restante do aplicativo (útil para dados pessoais confidenciais, bancos de dados com muitos usuários habilitados para leitura ...). Criptografar textos, binários, números, booleanos, datas ... Saiba mais.

Perfeitamente integrável a um aplicativo Spring, com recursos de integração específicos para o Spring 2, Spring 3.0 e Spring 3.1. Todos os digestores e criptografadores do jasypt são projetados para serem facilmente usados ​​(instanciados, injetados em dependência ...) da Spring. E, por serem seguros para threads, eles podem ser usados ​​sem preocupações de sincronização em um ambiente orientado a singleton como o Spring. Saiba mais: Primavera 2, Primavera 3.0, Primavera 3.1.

Integração opcional do Spring Security (anteriormente Acegi Security) para executar tarefas de criptografia de senha e correspondência para a estrutura de segurança, melhorando a segurança das senhas de seus usuários usando mecanismos de criptografia de senha mais seguros e fornecendo a você um maior grau de configuração e controle. Saber mais.

Fornece funcionalidade avançada para criptografar todos ou parte dos arquivos de configuração de um aplicativo, incluindo informações confidenciais como senhas de bancos de dados. Integre perfeitamente a configuração criptografada em aplicativos simples, baseados em Spring e / ou habilitados para Hibernate. Saber mais.

Fornece ferramentas CLI (Interface de linha de comando) fáceis de usar para permitir que os desenvolvedores inicializem seus dados criptografados e incluam operações de criptografia / descriptografia / digestão em tarefas ou scripts de manutenção. Saber mais.

Integra-se ao Apache Wicket, para criptografia mais robusta de URLs em seus aplicativos seguros.

Guias abrangentes e documentação javadoc, para permitir que os desenvolvedores entendam melhor o que estão realmente fazendo com seus dados.

Suporte robusto ao conjunto de caracteres, projetado para criptografar e digerir adequadamente os textos, seja qual for o conjunto de caracteres original. Suporte completo para idiomas como japonês, coreano, árabe ... sem problemas de codificação ou plataforma.

Nível muito alto de recursos de configuração: o desenvolvedor pode implementar truques como instruir um "criptografador" para solicitar a um servidor HTTPS remoto, por exemplo, a senha a ser usada para criptografia. Permite atender às suas necessidades de segurança.


1
Mas que segurança Jasyptfornece? Não consigo descobrir no site deles. É indistinguível sob ataques de texto simples escolhido? Integridade? Confidencialidade?
precisa saber é o seguinte

4

Aqui está minha implementação do meta64.com como Spring Singleton. Se você deseja criar uma instância de ciper para cada chamada que funcione também e remover as chamadas 'sincronizadas', mas cuidado com o 'cipher' não é seguro para threads.

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("singleton")
public class Encryptor {

    @Value("${aeskey}")
    private String keyStr;

    private Key aesKey = null;
    private Cipher cipher = null;

    synchronized private void init() throws Exception {
        if (keyStr == null || keyStr.length() != 16) {
            throw new Exception("bad aes key configured");
        }
        if (aesKey == null) {
            aesKey = new SecretKeySpec(keyStr.getBytes(), "AES");
            cipher = Cipher.getInstance("AES");
        }
    }

    synchronized public String encrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        return toHexString(cipher.doFinal(text.getBytes()));
    }

    synchronized public String decrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.DECRYPT_MODE, aesKey);
        return new String(cipher.doFinal(toByteArray(text)));
    }

    public static String toHexString(byte[] array) {
        return DatatypeConverter.printHexBinary(array);
    }

    public static byte[] toByteArray(String s) {
        return DatatypeConverter.parseHexBinary(s);
    }

    /*
     * DO NOT DELETE
     * 
     * Use this commented code if you don't like using DatatypeConverter dependency
     */
    // public static String toHexStringOld(byte[] bytes) {
    // StringBuilder sb = new StringBuilder();
    // for (byte b : bytes) {
    // sb.append(String.format("%02X", b));
    // }
    // return sb.toString();
    // }
    //
    // public static byte[] toByteArrayOld(String s) {
    // int len = s.length();
    // byte[] data = new byte[len / 2];
    // for (int i = 0; i < len; i += 2) {
    // data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i +
    // 1), 16));
    // }
    // return data;
    // }
}

3
Isso criptografará com o modo ECB, o que é horrível. Você deve ser a criação de modo menos CBC ou Modo GCM
Konstantino Sparakis

Obrigado pela sugestão Konstantinto, pesquisei isso no Google e encontrei um código que usa "AES / CBC / PKCS5Padding" como a string Init para Cipher, em vez de apenas "AES", mas analisarei mais sobre isso. Ou, se você quiser, pode fornecer a correção real, para que outras pessoas possam ver a melhor maneira. No entanto, além dos detalhes da CBC, acredito que minha solução é a mais simples e segura e merece ser votada acima de tudo.

Sim, não se preocupe, o Crypto é um assunto complicado. Infelizmente, todas as implementações nesta página estão quebradas e, infelizmente, é a primeira página exibida ao usar o google para pesquisar "como fazer criptografia java". Quando tiver uma chance, tentarei consertar todos eles.
Konstantino Sparakis

Meu exemplo é o mesmo: docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/… Exceto que eu precisava de Cipher.getInstance ("AES / ECB / PKCS5Padding"); Meu código pressupõe que há algum arquivo de propriedades com uma chave de criptografia de 16 bytes de comprimento, mas para criptografar uma sequência de uma senha de 'usuário fornecido', a página do Oracle (vinculada acima) mostra a maneira de fazer isso também.

1
Portanto, o problema com o BCE é que ele é extremamente vulnerável à análise de frequência. Há o famoso exemplo do pinguim do Linux, blog.filippo.io/the-ecb-penguin , veja como, embora a imagem esteja criptografada, você ainda pode dizer que é um pinguim. Fui em frente e escrevi meus pensamentos sobre o assunto abaixo :) stackoverflow.com/a/43779197/2607972
Konstantino Sparakis

4

Aqui, uma solução simples, com apenas java.*e javax.crypto.*dependências para criptografia de bytes, fornecendo confidencialidade e integridade . Deve ser indistinguível sob um ataque de texto simples escolhido para mensagens curtas na ordem de kilobytes.

Ele usa AESno GCMmodo sem preenchimento, uma chave de 128 bits é derivada PBKDF2com muitas iterações e um sal estático da senha fornecida. Isso garante que as senhas de força bruta sejam difíceis e distribua a entropia por toda a chave.

Um vetor de inicialização aleatória (IV) é gerado e será anexado ao texto cifrado. Além disso, o byte estático 0x01é acrescentado como o primeiro byte como uma 'versão'.

A mensagem inteira entra no código de autenticação de mensagem (MAC) gerado por AES/GCM.

Aqui vai, zero classe de criptografia de dependências externas, fornecendo confidencialidade e integridade :

package ch.n1b.tcrypt.utils;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * This class implements AES-GCM symmetric key encryption with a PBKDF2 derived password.
 * It provides confidentiality and integrity of the plaintext.
 *
 * @author Thomas Richner
 * @created 2018-12-07
 */
public class AesGcmCryptor {

    // /crypto/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode
    private static final byte VERSION_BYTE = 0x01;
    private static final int VERSION_BYTE_LENGTH = 1;
    private static final int AES_KEY_BITS_LENGTH = 128;


    // fixed AES-GCM constants
    private static final String GCM_CRYPTO_NAME = "AES/GCM/NoPadding";
    private static final int GCM_IV_BYTES_LENGTH = 12;
    private static final int GCM_TAG_BYTES_LENGTH = 16;

    // can be tweaked, more iterations = more compute intensive to brute-force password
    private static final int PBKDF2_ITERATIONS = 1024;

    // protects against rainbow tables
    private static final byte[] PBKDF2_SALT = hexStringToByteArray("4d3fe0d71d2abd2828e7a3196ea450d4");

    public String encryptString(char[] password, String plaintext) throws CryptoException {

        byte[] encrypted = null;
        try {
            encrypted = encrypt(password, plaintext.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException //
                | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException //
                | InvalidKeySpecException e) {
            throw new CryptoException(e);
        }
        return byteArrayToHexString(encrypted);
    }

    public String decryptString(char[] password, String ciphertext)
            throws CryptoException {

        byte[] ct = hexStringToByteArray(ciphertext);
        byte[] plaintext = null;
        try {
            plaintext = decrypt(password, ct);
        } catch (AEADBadTagException e) {
            throw new CryptoException(e);
        } catch ( //
                NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException //
                        | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException //
                        | BadPaddingException e) {
            throw new CryptoException(e);
        }
        return new String(plaintext, StandardCharsets.UTF_8);
    }

    /**
     * Decrypts an AES-GCM encrypted ciphertext and is
     * the reverse operation of {@link AesGcmCryptor#encrypt(char[], byte[])}
     *
     * @param password   passphrase for decryption
     * @param ciphertext encrypted bytes
     * @return plaintext bytes
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws IllegalArgumentException           if the length or format of the ciphertext is bad
     * @throws CryptoException
     */
    public byte[] decrypt(char[] password, byte[] ciphertext)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        // input validation
        if (ciphertext == null) {
            throw new IllegalArgumentException("ciphertext cannot be null");
        }

        if (ciphertext.length <= VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH) {
            throw new IllegalArgumentException("ciphertext too short");
        }

        // the version must match, we don't decrypt other versions
        if (ciphertext[0] != VERSION_BYTE) {
            throw new IllegalArgumentException("wrong version: " + ciphertext[0]);
        }

        // input seems legit, lets decrypt and check integrity

        // derive key from password
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);

        // init cipher
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec params = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8,
                ciphertext,
                VERSION_BYTE_LENGTH,
                GCM_IV_BYTES_LENGTH
        );
        cipher.init(Cipher.DECRYPT_MODE, key, params);

        final int ciphertextOffset = VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH;

        // add version and IV to MAC
        cipher.updateAAD(ciphertext, 0, ciphertextOffset);

        // decipher and check MAC
        return cipher.doFinal(ciphertext, ciphertextOffset, ciphertext.length - ciphertextOffset);
    }

    /**
     * Encrypts a plaintext with a password.
     * <p>
     * The encryption provides the following security properties:
     * Confidentiality + Integrity
     * <p>
     * This is achieved my using the AES-GCM AEAD blockmode with a randomized IV.
     * <p>
     * The tag is calculated over the version byte, the IV as well as the ciphertext.
     * <p>
     * Finally the encrypted bytes have the following structure:
     * <pre>
     *          +-------------------------------------------------------------------+
     *          |         |               |                             |           |
     *          | version | IV bytes      | ciphertext bytes            |    tag    |
     *          |         |               |                             |           |
     *          +-------------------------------------------------------------------+
     * Length:     1B        12B            len(plaintext) bytes            16B
     * </pre>
     * Note: There is no padding required for AES-GCM, but this also implies that
     * the exact plaintext length is revealed.
     *
     * @param password  password to use for encryption
     * @param plaintext plaintext to encrypt
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeySpecException
     */
    public byte[] encrypt(char[] password, byte[] plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
            InvalidKeySpecException {

        // initialise random and generate IV (initialisation vector)
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);
        final byte[] iv = new byte[GCM_IV_BYTES_LENGTH];
        SecureRandom random = SecureRandom.getInstanceStrong();
        random.nextBytes(iv);

        // encrypt
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);

        // add IV to MAC
        final byte[] versionBytes = new byte[]{VERSION_BYTE};
        cipher.updateAAD(versionBytes);
        cipher.updateAAD(iv);

        // encrypt and MAC plaintext
        byte[] ciphertext = cipher.doFinal(plaintext);

        // prepend VERSION and IV to ciphertext
        byte[] encrypted = new byte[1 + GCM_IV_BYTES_LENGTH + ciphertext.length];
        int pos = 0;
        System.arraycopy(versionBytes, 0, encrypted, 0, VERSION_BYTE_LENGTH);
        pos += VERSION_BYTE_LENGTH;
        System.arraycopy(iv, 0, encrypted, pos, iv.length);
        pos += iv.length;
        System.arraycopy(ciphertext, 0, encrypted, pos, ciphertext.length);

        return encrypted;
    }

    /**
     * We derive a fixed length AES key with uniform entropy from a provided
     * passphrase. This is done with PBKDF2/HMAC256 with a fixed count
     * of iterations and a provided salt.
     *
     * @param password passphrase to derive key from
     * @param salt     salt for PBKDF2 if possible use a per-key salt, alternatively
     *                 a random constant salt is better than no salt.
     * @param keyLen   number of key bits to output
     * @return a SecretKey for AES derived from a passphrase
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private SecretKey deriveAesKey(char[] password, byte[] salt, int keyLen)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        if (password == null || salt == null || keyLen <= 0) {
            throw new IllegalArgumentException();
        }
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, keyLen);
        SecretKey pbeKey = factory.generateSecret(spec);

        return new SecretKeySpec(pbeKey.getEncoded(), "AES");
    }

    /**
     * Helper to convert hex strings to bytes.
     * <p>
     * May be used to read bytes from constants.
     */
    private static byte[] hexStringToByteArray(String s) {

        if (s == null) {
            throw new IllegalArgumentException("Provided `null` string.");
        }

        int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Invalid length: " + len);
        }

        byte[] data = new byte[len / 2];
        for (int i = 0; i < len - 1; i += 2) {
            byte b = (byte) toHexDigit(s, i);
            b <<= 4;
            b |= toHexDigit(s, i + 1);
            data[i / 2] = b;
        }
        return data;
    }

    private static int toHexDigit(String s, int pos) {
        int d = Character.digit(s.charAt(pos), 16);
        if (d < 0) {
            throw new IllegalArgumentException("Cannot parse hex digit: " + s + " at " + pos);
        }
        return d;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    public class CryptoException extends Exception {

        public CryptoException(Throwable cause) {
            super(cause);
        }
    }
}

Aqui está o projeto inteiro com uma bela CLI: https://github.com/trichner/tcrypt

Editar: agora com o apropriado encryptStringedecryptString


Isto é incrível. Obrigado! Aprendi muito com seu código e depois de criar a classe BadVersionException Exception, seu código funcionou perfeitamente na primeira vez. Excelente!!
Morkus

Eu gosto dessa tentativa. Dito isto ... O sal deve ser aleatório, não estático. As iterações provavelmente também não devem ser estáticas. O GCM já inclui o IV no cálculo da tag. Porém, ele não contém o número da versão. Você não deve especificar o provedor para portabilidade, o "SunJCE" será o padrão nas plataformas que o suportam. Este código não contém nenhuma manipulação de cadeia de mensagens, necessária para esta pergunta em particular .
Maarten Bodewes

Tudo bem, eu limpa-lo um pouco mais e acrescentou o solicitado encryptStringe decryptString:)
trichner

Isso funcionou muito bem; ty para o código. Deve-se observar que esse código requer a API 19 (Kit Kat) ou superior para funcionar corretamente.
PGMacDesign 8/03/19

3

Eu consideraria usar algo como https://www.bouncycastle.org/ É uma biblioteca pré-construída que permite criptografar o que você quiser com um número de cifras diferentes. Entendo que você só quer se proteger da espionagem, mas se realmente deseja proteger as informações, o uso do Base64 não o protegerá.


1
Apenas recomendar uma biblioteca de criptografia aleatória com cifras não é uma resposta para a pergunta. Além disso, por que não usar as cifras incorporadas?
Maarten Bodewes

2

Aqui estão alguns links para você ler o que o Java suporta

Criptografar / descriptografar um fluxo de dados.

Este exemplo demonstra como criptografar (usando um algoritmo de criptografia simétrico como AES, Blowfish, RC2, 3DES etc.) uma grande quantidade de dados. Os dados são passados ​​em pedaços para um dos métodos de criptografia: EncryptBytes, EncryptString, EncryptBytesENC ou EncryptStringENC. (O nome do método indica o tipo de entrada (seqüência de caracteres ou matriz de bytes) e o tipo de retorno (seqüência de caracteres codificada ou matriz de bytes). As propriedades FirstChunk e LastChunk são usadas para indicar se um pedaço é o primeiro, o meio ou o último de um fluxo. Por padrão, FirstChunk e LastChunk são iguais a true - o que significa que os dados transmitidos são a quantia inteira.

JCERefGuide

Exemplos de criptografia Java


Sim, há criptografia suportada por Java. A criptografia de um fluxo também não foi solicitada.
Maarten Bodewes

2

Como muitos dos caras já disseram, você deve usar um codificador padrão que seja excessivamente usado como DES ou AES.

Um exemplo simples de como você pode criptografar e descriptografar uma string em java usando o AES .

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class EncryptorDemo {

    public static String encrypt(String key, String randomVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted text: "  + Base64.encodeBase64String(encrypted));
            return Base64.encodeBase64String(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String randomVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] originalText = cipher.doFinal(Base64.decodeBase64(encrypted));
            System.out.println("decrypted text: "  + new String(originalText));
            return new String(originalText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        String key = "JavasEncryptDemo"; // 128 bit key
        String randomVector = "RandomJavaVector"; // 16 bytes IV
        decrypt(key, randomVector, encrypt(key, randomVector, "Anything you want to encrypt!"));

    }
}

O CBC não é mais um modo seguro. O preenchimento é vulnerável ao preenchimento de ataques do Oracle. Além disso, o manuseio da chave e das mensagens em String não é seguro. Eles ficam na piscina de cordas e aparecem em um monte de lixo
Saptarshi Basu 27/10/1918

2
Aprecie o comentário. Este foi um exemplo simples dos métodos de criptografia e descriptografia de Java, conforme solicitado pelo usuário. A pergunta foi feita há 9 anos e foi respondida com base nisso. Obrigado.
viveknaskar

2
Sim, esta parece uma maneira simples de introduzir criptografia / descriptografia. Funcionou como um encanto para mim .... Obrigado.
Codewrapper

0

Aqui está uma solução de copiar / colar. Também recomendo ler e votar na resposta do @ Konstantino, mesmo que ela não forneça nenhum código. O vetor de inicialização (IV) é como um sal - não precisa ser mantido em segredo. Eu sou novo no GCM e, aparentemente, o AAD é opcional e usado apenas em determinadas circunstâncias. Defina a chave na variável de ambiente SECRET_KEY_BASE. Use algo como o KeePass para gerar uma senha de 32 caracteres. Esta solução foi modelada após a minha solução Ruby.

    public static String encrypt(String s) {
        try {
            byte[] input = s.getBytes("UTF-8");
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            // generate IV
            SecureRandom secureRandom = SecureRandom.getInstanceStrong();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            secureRandom.nextBytes(ivBytes);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes); // 96 bit tag length
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
            // generate AAD
//          byte[] aadBytes = new byte[cipher.getBlockSize()];
//          secureRandom.nextBytes(aadBytes);
//          cipher.updateAAD(aadBytes);
            // encrypt
            byte[] encrypted = cipher.doFinal(input);
            byte[] returnBytes = new byte[ivBytes.length + encrypted.length];
//          byte[] returnBytes = new byte[ivBytes.length + aadBytes.length + encrypted.length];
            System.arraycopy(ivBytes, 0, returnBytes, 0, ivBytes.length);
//          System.arraycopy(aadBytes, 0, returnBytes, ivBytes.length, aadBytes.length);
            System.arraycopy(encrypted, 0, returnBytes, ivBytes.length, encrypted.length);
//          System.arraycopy(encrypted, 0, returnBytes, ivBytes.length+aadBytes.length, encrypted.length);
            String encryptedString = Base64.getEncoder().encodeToString(returnBytes);
            return encryptedString;
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "encrypt()", "Could not encrypt string: " + e.getMessage());
            return null;
        }
    }

    public static String decrypt(String s) {
        if (s == null || s.length() == 0) return "";
        try {
            byte[] encrypted = Base64.getDecoder().decode(s);
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            System.arraycopy(encrypted, 0, ivBytes, 0, ivBytes.length);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
//          cipher.updateAAD(encrypted, ivBytes.length, cipher.getBlockSize());
            byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize(), encrypted.length - cipher.getBlockSize());
//          byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize()*2, encrypted.length - cipher.getBlockSize()*2);
            String decryptedString = new String(decrypted, "UTF-8");
            return decryptedString;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "decrypt()", "Could not decrypt string: " + e.getMessage());
            return null;
        }
    }

Aqui está um exemplo:

    String s = "This is a test.";
    String enc = Utils.encrypt(s);
    System.out.println(enc);
    // fQHfYjbD+xAuN5XzH2ojk/EWNeKXUrKRSfx8LU+5dpuKkM/pueCMBjKCZw==
    String dec = Utils.decrypt(enc);
    System.out.println(dec);
    // This is a test.

-4

Você pode considerar uma ferramenta automatizada para gerar o código de criptografia / descriptografia, por exemplo. https://www.stringencrypt.com/java-encryption/

Ele pode gerar diferentes códigos de criptografia e descriptografia a cada vez para a criptografia de cadeia ou arquivo.

É bastante útil quando se trata de criptografia rápida de cadeias sem usar RSA, AES etc.

Resultados da amostra:

// encrypted with https://www.stringencrypt.com (v1.1.0) [Java]
// szTest = "Encryption in Java!"
String szTest = "\u9E3F\uA60F\uAE07\uB61B\uBE1F\uC62B\uCE2D\uD611" +
                "\uDE03\uE5FF\uEEED\uF699\uFE3D\u071C\u0ED2\u1692" +
                "\u1E06\u26AE\u2EDC";

for (int iatwS = 0, qUJQG = 0; iatwS < 19; iatwS++)
{
        qUJQG = szTest.charAt(iatwS);
        qUJQG ++;
        qUJQG = ((qUJQG << 5) | ( (qUJQG & 0xFFFF) >> 11)) & 0xFFFF;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 6) | (qUJQG << 10)) & 0xFFFF;
        qUJQG ^= iatwS;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 3) | (qUJQG << 13)) & 0xFFFF;
        qUJQG ^= 0xFFFF;
        qUJQG ^= 0xB6EC;
        qUJQG = ((qUJQG << 8) | ( (qUJQG & 0xFFFF) >> 8)) & 0xFFFF;
        qUJQG --;
        qUJQG = (((qUJQG & 0xFFFF) >> 5) | (qUJQG << 11)) & 0xFFFF;
        qUJQG ++;
        qUJQG ^= 0xFFFF;
        qUJQG += iatwS;
        szTest = szTest.substring(0, iatwS) + (char)(qUJQG & 0xFFFF) + szTest.substring(iatwS + 1);
}

System.out.println(szTest);

Usamos o tempo todo em nossa empresa.


Isso é segurança através da obscuridade e não é realmente seguro.
Chloe

Essa pergunta está solicitando criptografia moderna de força criptográfica moderna, como o AES, e não apenas ofuscação para dificultar a extração estatal de seqüências de caracteres. Isso nem parece manter qualquer estado entre os caracteres, portanto é suscetível à análise de frequência. ( Cifra de substituição de alfabeto único , exceto sobre pontos de código UTF-16 em vez do alfabeto latino. Mas se você o usar no texto em inglês ASCII, você obterá apenas alguns valores únicos de caracteres de 16 bits, a menos que eu esteja interpretando errado isso)
Peter Cordes

-4
String s1="arshad"; 
char[] s2=s1.toCharArray(); 
int s3= s2.length; 

  System.out.println(s3);
 int i=0; 

// for(int j=0;j<s3;j++) 
// System.out.println(s2[j]); 

for(i=0;i<((s3)/2);i++) { 

char z,f=10; 
z=(char) (s2[i] * f); 
s2[i]=s2[(s3-1)-i]; 
s2[(s3-1)-i]=z; 

String b=new String(s2);

 print(b);  }

Formalmente, ele criptografa os dados em um formato ilegível. Para descriptografar, use o mesmo código. E mude s [i] * f para s [I] / f.
Arshad shaik

Isso é segurança através da obscuridade e não é realmente seguro.
Chloe

-5
public static String encryptParams(String myTextInput) {

        String myKey = "40674244454045cb9a70040a30e1c007";
        String myVector = "@1B2c3D4e5F6g7H8";

        String encData = "";

        try{
            JavaEncryprtionUtil encUtil = new JavaEncryprtionUtil();
            encData = Base64.encodeToString(encUtil.encrypt(myTextInput.getBytes("UTF-8"), myKey.getBytes("UTF-8"), myVector.getBytes("UTF-8")),Base64.DEFAULT);
            System.out.println(encData);
        }catch(NoSuchAlgorithmException ex){
            ex.printStackTrace();
        }catch(NoSuchPaddingException ex){
            ex.printStackTrace();
        }catch(InvalidKeyException ex){
            ex.printStackTrace();
        }catch(InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
        }catch(IllegalBlockSizeException ex){
            ex.printStackTrace();
        }catch(BadPaddingException ex){
            ex.printStackTrace();
        }catch(UnsupportedEncodingException ex){
            ex.printStackTrace();
        }

        return encData;
    }

1
o JavaEncryprtionUtil faz parte da API do JDK? caso contrário, você deve soletrar o nome da biblioteca.
Little Student Fermat

4
Não é possível encontrar essa classe. Parece que a resposta é inventada.
Json.garriss
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.