Tag: segurança

Apache POI: adicionando segurança em arquivos Excel

poi-original
A biblioteca Apache POI possibilita a leitura e gravação de documentos do Microsoft Office via código Java.

Por esses dias, fiz uma pesquisa sobre como essa biblioteca viabiliza (ou não) trabalharmos com arquivos protegidos do Excel.

Níveis de segurança do Office

Documentos do Excel podem ter diferentes níveis de proteção, a saber:

  • Proteção contra alteração: permite abrir o documento em modo somente-leitura, mas não deixa o usuário alterá-lo sem antes digitar a senha.
  • Proteção contra alteração de um trecho do documento: protege planilhas e células específicas com senha.
  • Proteção contra leitura: não permite abrir o documento sem a senha.

Todos esses modos podem ser usados ao mesmo tempo, inclusive com senhas diferentes.

Se o arquivo tiver proteção contra leitura, o Excel irá solicitar a senha antes de abrir o mesmo. Se houver proteção contra alteração ele irá solicitar a senha para desbloqueio da edição ou permitir acesso somente-leitura ao documento. Se planilhas ou células estiverem protegidas contra alteração, então o usuário deve acessar a função “Desproteger” no Excel e digitar a senha de desbloqueio para cada trecho.

As proteções contra alteração são simples travas no editor, pois o arquivo em si permanece aberto e não há restrições de alteração via programação ou com o uso de editores de terceiros. Podemos dizer que este mecanismo oferece um baixíssimo grau de segurança, que impedirá apenas o usuário mais leigo de efetivamente modificar o documento.

Por outro lado, a proteção contra leitura oferece um grau de segurança maior, pois o documento como um todo é criptografado. Isso significa que nenhum programa ou editor conseguirá sequer ler o documento sem antes descriptografar os bytes do arquivo com a senha original.

Versões dos documentos do Office

Quem acompanha o Office há algum tempo deve ter notado que, a partir da versão 2007, os documentos ganharam novas extensões, com o sufixo x. Por exemplo, doc passou a ser docx, xls mudou para xlsx e assim por diante.

Mas não foi apenas a extensão que mudou. O conteúdo foi completamente reformulado. O formato mais antigo era do tipo binário, enquanto os novos são baseados em XML.

As vantagens do formato XML são inúmeras, a começar pela capacidade de qualquer ferramenta que trabalha com XML conseguir processar o documento ou pelo menos parte dele.

No que se refere à biblioteca Apache POI, isso também nos afeta diretamente, pois há APIs diferentes para trabalhar com os diferentes formatos de documentos.

Como veremos adiante, nem todos os formatos suportam todos os tipos de segurança.

Capacidades do POI

A conclusão dos meus testes foi a seguinte:

Tipo de documento Operação Proteção contra alteração do documento Proteção contra alteração de uma planilha Proteção contra Leitura (criptografia)
XLS
Leitura OK
Criação N/D* OK N/D
XLSX
Leitura OK
Criação N/D OK OK

Na leitura de um arquivo XLS ou XLSX, as proteções contra alteração são ignoradas, então não há o que testar.
N/D = Não disponível, isto é, não há suporte na API.
* Existe um método para proteger o documento, mas ele não surte efeito (bug).

Como pode ser visto, para documentos no formato legado XLS o POI suporta apenas a leitura de arquivos criptografados. Na criação, ele apenas consegue proteger parte do documento contra alteração.

Já para o formato mais novo XLSX, o POI consegue ler e criar arquivos criptografados. Entretanto, na criação do documento, não há um método para proteger o documento todo contra alteração.

Alguns fóruns sugerem uma possibilidade de contornar a falta do recurso de proteger o documento como um todo contra alteração. Para isso, basta criar um arquivo Excel com tal proteção e usá-lo como template para geração de um novo.

Implementação

Criptografia em arquivos XLS

O POI suporta apenas ler arquivos criptografados neste formato, isto é, que estão protegidos contra leitura.

O segredo é a classe Biff8EncryptionKey. Basta definir a senha através do método estático setCurrentUserPassword e depois ler o arquivo normalmente.

Exemplo:

try {
    Biff8EncryptionKey.setCurrentUserPassword(password);
    new HSSFWorkbook(input);
} finally {
    Biff8EncryptionKey.setCurrentUserPassword(null);
}

É bom não esquecer de remover a senha ao final para não atrapalhar futuras leituras.

Mesmo sendo um método estático, não deve haver problemas de concorrências, pois a documentação afirma que a senha é armazenada usando ThreadLocal.

Criptografia em arquivos XLSX

O POI implementa tanto a leitura quanto a criação de documentos criptografados neste formato.

Veja minha implementação para a leitura:

public class XlsxDecryptor {

    public static XSSFWorkbook decrypt(InputStream input, String password)
            throws IOException {

        POIFSFileSystem filesystem = new POIFSFileSystem(input);
        EncryptionInfo info = new EncryptionInfo(filesystem);
        Decryptor d = Decryptor.getInstance(info);

        try {
            if (!d.verifyPassword(password)) {
                throw new RuntimeException("Unable to process: document is encrypted");
            }

            InputStream dataStream = d.getDataStream(filesystem);
            return new XSSFWorkbook(dataStream);
        } catch (GeneralSecurityException ex) {
            throw new RuntimeException("Unable to process encrypted document",
                ex);
        }

    }

    private XlsxDecryptor() {
    }

}

E para criação:

public class XlsxEncryptor {

    public static void encrypt(InputStream input, OutputStream output, 
            String password) throws IOException {

        try {
            POIFSFileSystem fs = new POIFSFileSystem();
            EncryptionInfo info = new EncryptionInfo(fs, EncryptionMode.agile);

            Encryptor enc = info.getEncryptor();
            enc.confirmPassword(password);

            OPCPackage opc = OPCPackage.open(input);
            OutputStream os = enc.getDataStream(fs);
            opc.save(os);
            opc.close();

            fs.writeFilesystem(output);
            output.close();
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        } catch (InvalidFormatException e) {
            throw new RuntimeException(e);
        }

    }

}

Proteção de partes de um documento

O POI implementa a proteção de uma planilha de um documento Excel e a proteção de células. Testei apenas a proteção de planilhas.

A implementação é muito simples, basta usar o método protectSheet. Veja o exemplo para o formato XLSX:

XSSFSheet sheet = workbook.createSheet();
sheet.protectSheet("54321");

E agora o equivalente para o formato XLS:

HSSFSheet sheet = workbook.createSheet();
sheet.protectSheet("54321");

Testes e exemplo

Antes de encerrar o artigo, vou acrescentar aqui dois dos testes unitários que implementei de forma que possam servir como exemplo de uso do POI e do meu projeto.

O primeiro cria um novo arquivo XLSX cripgrafado e em seguida lê o mesmo arquivo verificando se o valor foi gravado corretamente:

@Test
public void createXLSXOpenAndModifyProtected() throws IOException {
    System.out.println("createXLSXOpenAndModifyProtected");

    //creates sheet
    XSSFWorkbook workbook = new XSSFWorkbook();
    XSSFSheet sheet = workbook.createSheet();
    sheet.protectSheet("54321");

    XSSFRow row = sheet.createRow(0);
    XSSFCell cell = row.createCell(0);
    cell.setCellValue("Gravado");

    //saves sheet
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    workbook.write(bos);
    bos.close();
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());

    new File("target/.output-file/xlsx").mkdirs();
    WorksheetEncryptor.encrypt(
        bis, 
        new FileOutputStream("target/.output-file/xlsx/OpenAndModifyProtected.xlsx"), 
        DocumentType.XML, 
        "54321");
    bis.close();

    //read again and check
    XSSFWorkbook workbook2 = (XSSFWorkbook) WorksheetDecryptor.decrypt(
        new FileInputStream("target/.output-file/xlsx/OpenProtected.xlsx"),
        DocumentType.XML, 
        "54321");
    Assert.assertEquals("Gravado", workbook2.getSheetAt(0).getRow(0).getCell(0).getStringCellValue());
}

Em seguida, o segundo lê um arquivo XLS criptografado e verifica o valor da célula:

@Test
public void testOpenAndModifyProtectedXLS() throws IOException {
    System.out.println("### testOpenAndModifyProtectedXLS");
    HSSFWorkbook workbook = (HSSFWorkbook) WorksheetDecryptor.decrypt(
        getClass().getResourceAsStream("xls/OpenAndModifyProtected.xls"),
        DocumentType.LEGACY, 
        "12345");
    String conteudoCelula = workbook.getSheetAt(0).getRow(0).getCell(0)
        .getStringCellValue();
    Assert.assertEquals("Teste criptografia", conteudoCelula);
}

Código-fonte

Todo o código, inclusive os testes, está disponível em um projeto no meu GitHub.

Com o Maven você será capaz de executá-lo sem nenhuma configuração adicional. Basta clonar o repositório.

Criando e mantendo senhas seguras

Muito se discute hoje sobre a segurança da informação. Nós, meros mortais, usuários de um número cada vez maior de serviços online, temos boa parte de nossa segurança à mercê de uma grande variedade de empresas de tecnologia esparramadas mundo afora.

Um problema crescente desde o advento da Internet envolve o gerenciamento de senhas de autenticação para acesso a esses serviços. Do ponto de vista técnico, políticas de segurança devem ser tomadas, tais como impor um tamanho mínimo, exigir caracteres especiais, dígitos numéricos e variação de capitalização (mistura de maiúsculas e minúsculas). Pode-se ainda incluir uma autenticação adicional como um token (chaveiro, cartão, aplicativo para celular) ou dispositivo biométrico. Cada iniciativa para dificultar ações maliciosas tem o potencial de diminuir o número de ocorrências de segurança, mas os riscos nunca serão definitivamente eliminados.

Por outro lado, um peso considerável sobre a segurança recai sobre nós. Mas, como podemos nos proteger? Como criamos senhas seguras? Primeiro, precisamos entender alguns conceitos sobre senhas.

As pessoas usam senhas fáceis e repetidas

Quando a Sony foi invadida em 2011, contas de 77 milhões de usuários foram roubadas, incluindo seus números de cartão de crédito, e-mails, logins e senhas. Pouco tempo depois, mais um milhão de usuários tiveram seus dados capturados e expostos a qualquer interessado através de um arquivo torrent.

Um especialista em segurança analisou os dados disponibilizados e descobriu alguns fatos interessantes:

  • Mais de 50% dos usuários usam senhas com 7 caracteres ou menos
  • 50% das senhas consistem apenas de letras minúsculas e 45% apenas de letras maiúsculas
  • Apenas 4% das senhas continham alguma diversidade (números, maiúsculas e minúsculas, caracteres especiais)
  • 1% das senhas continham caracteres especiais
  • 36% das senhas estavam presentes em dicionários de senhas usados por hackers

Na mesma análise, comparando as senhas obtidas em dois serviços diferentes da Sony, verificou-se que 92% dos usuários usavam a mesma senha em ambos. Comparando com senhas obtidas de outro serviço fora da Sony, o reuso foi de 67%.

Definindo uma senha segura

Qualquer especialista em segurança lhe dirá que não existe nada 100% seguro. Para efeito de estudo, vamos considerar que uma senha segura é aquela onde o esforço para obtê-la é mais custoso do que o benefício advindo de tal ato.

Na área de segurança é comum usarmos o conceito de entropia para explicar a dificuldade de descobrir-se uma senha. Este termo é emprestado da física, mais especificamente da Segunda Lei da Termodinâmica, e define o grau de irreversibilidade ou desordem de um sistema. Quanto maior a entropia de uma senha, mais aleatória e complexa ela é, assim como será maior o esforço de alguém para obtê-la.

Fatores que aumentam a entropia de uma senha

A Universidade de Cambridge realizou um estudo empírico sobre segurança de senhas onde 288 estudantes foram divididos em 3 grupos: o primeiro poderia escolher uma senha qualquer, o segundo usaria um método para gerar uma senha aleatória e o terceiro usaria uma senha cujas letras eram as iniciais de uma frase qualquer.

O ataque mais efetivo foi o uso de um dicionário de palavras comuns, que conseguiu descobrir 30% das senhas escolhidas pelos alunos (primeiro grupo), 8% das senhas aleatórias e 6% das senhas com iniciais de uma frase. Em seguida, um ataque de força bruta foi realizado. Neste tipo de ataque, senhas sequenciais são geradas com base num conjunto de caracteres. Quanto menor a senha, mais suscetível ela é a um ataque de força bruta. Todas as senhas com menos de 6 caracteres foram quebradas.

A conclusão que podemos chegar até aqui é que uma senha segura contém 8 ou mais caracteres e deve ter variações de tipos de caracteres (maiúsculas, minúsculas, números e caracteres especiais). Mas senhas precisam ser fáceis de memorizar, e agora?

A questão da memorização

O quadrinho abaixo, em inglês, exemplifica uma ideia criativa para criar senhas seguras e fáceis de memorizar:

Criando uma senha segura

A ideia do autor, também defendida por uma parte da comunidade de segurança, é não usar uma senha complexa cheia variações especiais, pois ela seria de dificílima memorização para humanos, mas fácil de quebrar para computadores. Por outro lado, uma sequência de palavras de fácil memorização para humanos seria mais segura do ponto de vista computacional.

A princípio alguém poderia argumentar que selecionar quatro palavras não é tão diferente de uma senha de 4 dígitos, certo? Errado!

Análise combinatória da entropia das senhas: qual tipo de senha é mais segura?

Uma senha consiste numa combinação de elementos cuja ordem importa e onde existe repetição. Este é o conceito de arranjo com repetição, cuja fórmula é:

T = nr, sendo:

T = total de combinações (quanto maior, mais difícil de adivinhar)

n = quantidade de elementos (tamanho da senha)

r = número de elementos selecionados (número de possibilidades de cada caractere da senha)

Vamos aplicar esta fórmula a alguns tipos de senhas e obter o número de combinações possíveis:

  • Senha de 4 caracteres composta apenas de letras maiúsculas ou minúsculas (26 elementos selecionados)
        T = 426
  • Senha de 8 caracteres composta apenas de letras maiúsculas ou minúsculas (26 elementos selecionados)
        T = 826
  • Senha de 8 caracteres composta de letras, números e caracteres especiais “!”, “@”, “#”, “$”, “%”, “&”, “*”, “_”, “.” e “-” (72 elementos selecionados)
        T = 872
  • Senha de 4 palavras, considerando as 3 mil palavras mais comuns de uma língua
        T = 43.000
  • Senha de 4 palavras, selecionadas de um mini dicionário (um “mini Aurélio” possui 30 mil verbetes)
        T = 430.000

Os itens acima estão ordenados, de forma que o primeiro oferece a menor entropia e o último a maior. Portanto, senhas com palavras, ainda que contidas num dicionário, são muito mais seguras do que senhas complicadas que misturam números, letras maiúsculas e minúsculas e alguns caracteres especiais. Além disso, uma sequência de palavras é muito mais fácil de memorizar.

Senhas diferentes para sites diferentes

Você usa a mesma senha de outros sites quando faz um novo cadastro em um site qualquer? Já pensou que, se apenas uma das contas que você possui for comprometida, o hacker terá acesso a todas as suas outras contas? Voltamos ao problema da memorização: se devemos usar senhas diferentes para cada site, como poderemos lembrar-nos de cada uma delas?

Os simplistas dizem: simplesmente anote suas senhas num papel e guarde na carteira. Bem, se você já perdeu a carteira ou foi roubado, isso soa um tanto absurdo, não é mesmo?

A primeira solução apontada pelos especialistas é usar um aplicativo gerenciador de senhas. Você memoriza uma senha mestra e a usa para acessar as demais senhas. Porém, é ruim ficar dependente de um aplicativo. Se for um aplicativo para desktop você nem sempre vai estar com sua máquina ao seu lado, se for mobile alguém pode roubar seu celular e se for na nuvem (web) existem outras questões de segurança a serem consideradas.

Outra sugestão apontada em fóruns de segurança é criar um “algoritmo pessoal” baseado no site. Suponha que sua senha do Facebook seja “Eu amo o Facebook!”, então sua senha do Gmail é “Eu amo o Gmail!”. Bem, o problema com essa abordagem é óbvio: qualquer um poderia adivinhar sua senha em outro site. Soluções paliativas consistem em criar um algoritmo mais difícil do que simplesmente usar o nome do site, mas isso sempre irá cair na categoria de segurança por obscuridade, o que não é nada bom do ponto de vista da Segurança da Informação.

Uma abordagem usada por muitos, às vezes inconscientemente, é adotar três senhas principais. A primeira é uma senha com baixa entropia, usada em cadastros diversos na internet que não trazem grande risco. A segunda é de média entropia usada em serviços mais importantes e pessoais, como a conta do Facebook, blog pessoal, e-commerce, etc. A última é uma senha de alta entropia usada em serviços críticos e essenciais, como o e-mail pessoal, bancos, etc. Porém esta solução ainda não é a ideal, pois um vazamento de informação levaria a uma brecha de segurança em serviços do mesmo nível.

Além das senhas

Se você conseguiu acompanhar as informações até aqui, deve ter percebido o tamanho da dificuldade por parte dos provedores de serviços e dos usuários em gerenciar senhas. Mas, existe alguma alternativa? Felizmente, sim! Inclusive alguns acreditam que o uso de senhas é algo que deva ser abandonado.

Uma das opções que temos é o uso de certificados digitais. Por exemplo, em alguns acessos SSH e VPN configura-se o servidor para aceitar um determinado certificado e o usuário que possui esse certificado em seu sistema poderá acessar o serviço. Porém, o uso de certificados por usuários hoje é um tanto restrito a administradores de serviços e redes ou a serviços importantes como a emissão de notas fiscais online. Isso ocorre, em parte, pelo esforço que seria necessário para gerenciar os certificados por parte dos administradores de rede e, em parte, pelo custo junto às autoridades certificadoras. Além disso, certificados só funcionam bem se o computador ou dispositivo for usado somente por uma pessoa, já que eles ficam armazenados no disco.

Dispositivos biométricos são outra alternativa. Eu, que sou cliente do banco Itaú, já utilizo a autenticação por impressão digital há alguns meses no caixa eletrônico, entretanto ainda estamos um pouco longe de ter tal tecnologia disponível em todos os serviços do dia-a-dia.

Outra solução mais recente é a autenticação colaborativa, cujos exemplos mais conhecidos são OpenID e OAuth. Nesse sistema, você usa uma conta única em diferentes serviços. É o caso do “Login com Facebook” ou “Login com Google”. Ao se cadastrar em um site com suporte a essas tecnologias, você não precisa criar um novo cadastro e fornecer a senha para aquele site, apenas autoriza o Facebook, Google ou outro serviço onde você possui uma conta a compartilhar algumas de suas informações.  Este mecanismo é muito interessante, pois você precisa gerenciar apenas uma senha “forte” e os demais serviços nunca chegam a processar sua senha. Num caso de invasão a um deles, um hacker não terá a menor chance de invadir outros serviços. O risco verdadeiro está em sua conta principal, mas pelo menos você sabe em quem está confiando. Porém, existe uma dificuldade em usar o OpenID de forma abrangente, pois as intranets não irão querer depender de um serviço externo para autenticarem seus usuários.

Conclusões

A criação de senhas seguras, usando diversidade de caracteres e um comprimento razoável, é importante para a segurança de nossas contas. Uma senha composta de quatro ou mais palavras é mais segura que uma senha complexa e aleatória de oito dígitos.

Mas o problema está mais embaixo. Deveríamos criar senhas diferentes para cada serviço, o que, por sua vez, dificulta a memorização. Uma solução é usarmos grupos de senhas, isto é, uma senha simples para serviços de pouca importância, outra mais segura para serviços essenciais e uma senha complexa para serviços críticos.

Existem alternativas melhores ao uso de senhas, como certificados, dispositivos biométricos e os padrões OpenID e OAuth. Infelizmente o uso desses mecanismos de autenticação é limitado por diversos fatores no mundo atual.

Enfim, não há uma solução fácil disponível hoje. Aliás, se você tiver uma, me conte e vamos ficar bilionários! 😉

Sempre que um programador concatena um parâmetro da requisição na query, uma fada morre

fada-parametro-query
E isso vale para qualquer dado vindo de fora do sistema: banco de dados, arquivos, web services e por aí vai.

Creative Commons O blog State of the Art de Luiz Ricardo é licenciado sob uma Licença Creative Commons. Copie, compartihe e modifique, apenas cite a fonte.