1. Introdução
Hoje, a segurança da informação é uma das prioridades do setor de TI, especialmente considerando que, para ouvir o tráfego em nossos dias, você praticamente não precisa de conhecimento especializado - na Internet, existem softwares e manuais detalhados suficientes.
Portanto, eu configurei e resolvi o problema de escrever um serviço para a troca de arquivos projetados de forma que ele fosse protegido ao máximo contra ataques do tipo "homem no meio" - os arquivos transmitidos pelo serviço não devem sair da placa de rede do dispositivo final em forma não criptografada e a descriptografia, portanto, teve que ocorrer na máquina do destinatário final.
Seleção de ferramenta
Para escrever o lado do servidor do aplicativo, escolhi a linguagem de programação Java em combinação com a estrutura de desenvolvimento da Web Wicket. Este não é o único kit de ferramentas no qual foi possível implementar as tarefas, mas essa pilha é suficiente para resolvê-las, e o Wicket, entre outras coisas, fornece uma interface muito conveniente para trabalhar com componentes de páginas da web. O frontend não implicava a presença de algo complicado - eu não me propus a tarefa de desenvolver uma interface bonita e amigável, a lógica do aplicativo era uma prioridade mais alta, então me limitei ao clássico pacote HTML / CSS / JS.
O servidor está sendo executado no Ubunty 18.04, no contêiner Docker - mas a configuração do servidor será discutida em mais detalhes abaixo - usando o contêiner de servlet Apache Tomcat. Outras tecnologias necessárias para criar esse aplicativo, se necessário, serão mencionadas durante o artigo.
Configuração do servidor
Como já mencionado, o aplicativo está localizado em um servidor Linux, em particular, no Ubunty 18.04. Para isolar o sistema de outros aplicativos, além de simplificar a implantação, foi decidido contêiner do processo - com a ajuda do Doker alocou ao aplicativo uma certa quantidade de CPU e memória, abriu a lista de portas e fechou o restante. Não descreverei em detalhes a instalação e a configuração do serviço Docker, mas devo mencionar os pontos principais.
Supõe-se que todos os comandos sejam executados com privilégios de administrador.
Para começar, era necessário instalar e iniciar o serviço correspondente, com comandos
Depois disso, baixe e execute a imagem do sistema operacional - não precisamos mais do contêiner no momento:
O estilo do prompt de entrada deveria ter mudado - agora estamos no contêiner e podemos trabalhar sem medo pelo ambiente real. Talvez alguns pacotes com os quais estamos acostumados em uma máquina real no contêiner sejam perdidos, mas o procedimento para instalá-los não é diferente de instalar um pacote em uma máquina real, portanto não vou me concentrar nisso. Supõe-se ainda que todas as ações sejam executadas dentro do contêiner.
Configurando o Tomcat Servlet Container
Portanto, no momento, assumimos que estamos trabalhando em um sistema operacional limpo, onde tudo está por padrão. Para poder hospedar um aplicativo Java nele, é necessário instalar o jdk e o jvm diretamente nele e definir variáveis de ambiente com caminhos de instalação. Portanto, aqui darei apenas comandos principais, com mais detalhes o processo de instalação do Java no Linux pode ser considerado na rede.
Para instalar o JDK e o JRE e depois ajustar a variável de ambiente JAVA_HOME, é necessário inserir estes comandos de acordo:
Então você pode ir diretamente para instalar o Tomcat.
A principal coisa a prestar atenção - conceder direitos de gerente e administrador - por padrão, você não pode administrar aplicativos remotamente, não na máquina local. Caso contrário, tudo é padrão - baixando e instalando o pacote e iniciando o serviço.
Escrevendo um programa
O programa, como mencionado acima, consistirá em um servidor e uma parte do cliente. Consideraremos brevemente o lado do cliente; o arquivo do Editor e as funções de criptografia são de interesse máximo.
No entanto, as primeiras coisas primeiro.
Vamos começar pelo lado do cliente.
Conforme planejado, deve dar acesso à lista de arquivos armazenados no servidor. Como a solução deveria ser barata, transferi a lista de arquivos em forma de texto para uma caixa de texto inativa e anexei um formulário na parte inferior para selecionar o nome do arquivo a ser aberto.
Fig. 1 - Interface de armazenamento de arquivosQuando você insere um nome de arquivo na linha de entrada, o arquivo é aberto em outra janela - no editor. Código Java para a página que lista os arquivos:
File directory = new File(wayToDirs); String files = new String(); directory.mkdirs(); for (File i : directory.listFiles()) files+=i.getName()+"\n"; TextArea listOfFiles = new TextArea("files", Model.of("")); listOfFiles.setEnabled(false); listOfFiles.setDefaultModelObject(files); add(listOfFiles);
O HTML correspondente é ainda mais trivial:
<textarea wicket:id = "files" class="files" id = "files" /> <form wicket:id="selector" > <textarea wicket:id="name" class="input"/> <button wicket:id="opener" class="input" style="width: 10%"></button> </form>
Como já mencionado acima, a página com o editor e a criptografia representa diretamente o interesse. Lá, ocorre a abertura, modificação, criptografia e descriptografia do arquivo necessário, bem como seu armazenamento.
Fig. 2 - interface do editorVamos começar pelo lado do servidor - é mais fácil de entender. Do ponto de vista do servidor, o aplicativo deve clicar na tecla para aceitar texto da janela de entrada de texto, aceitar o nome do arquivo da linha correspondente e salvar o documento resultante. Na verdade, é isso que acontece na lista abaixo.
TextField wayToSaveFile = new TextField("wayToSaveFile", Model.of("")); Button saveButton = new Button("save") { @Override public void onSubmit() { super.onSubmit(); File file = new File(DirectoryInterface.wayToDirs+"/" + wayToSaveFile.getInput()); try { FileWriter fw = new FileWriter(file); fw.write(textArea.getDefaultModelObject().toString()); fw.close(); } catch (Exception e) { System.out.println(e.getMessage()); debud.setDefaultModelObject(e.getMessage()); } } };
Mas no momento da gravação, o arquivo já está criptografado. Para fazer isso, os botões “Criptografar” e “Descriptografar” são implementados na parte do cliente, que são usados para criptografia e descriptografia. A criptografia ocorre de acordo com o algoritmo Vigenere, falarei um pouco mais sobre isso separadamente, mas é mostrado nas funções.
Assim, o código do cliente se parece com isso:
<script> function encryptFun(){ let outputString = ""; let inputString = document.getElementById("text").value; let password = document.getElementById("passwordTextField").value; for (let i = 0; i<inputString.toString().length-1; i++) outputString+=(String.fromCharCode((inputString.toString().charCodeAt(i)+256-password.toString().charCodeAt(i%password.toString().length))%256)); document.getElementById("text").value = outputString; return outputString; }; function decryptFun(){ let outputString = ""; let inputString = document.getElementById("text").value; let password = document.getElementById("passwordTextField").value; for (let i = 0; i<inputString.toString().length-1; i++) outputString+=(String.fromCharCode((inputString.toString().charCodeAt(i)+256+password.toString().charCodeAt(i%password.toString().length))%256)); document.getElementById("text").value = outputString; return outputString; }; </script> … <!— - <input wicket:id="wayToSaveFile"class="input"> <button wicket:id = "save" class="input">Save</button> <input id = "passwordTextField" class="input"> <button id = "encrypt" onclick="encryptFun()" class="input"></button> <button id = "decrypt" onclick="decryptFun()" class="input"></button>
Assim, depois de pressionar o botão de criptografia, o texto no campo de entrada é substituído pelo texto retornado pela função de criptografia - e vice-versa.
A versão atual do aplicativo não determina automaticamente se o texto está criptografado; portanto, qualquer texto que não esteja conectado é considerado criptografado.
Algoritmo de criptografia
Eu escolhi o algoritmo de criptografia Vigenere - que é uma variação da cifra de César com um deslocamento variável por palavra-chave. O código fonte de entrada e a chave de tamanho arbitrário. Em seguida, o i-ésimo membro do texto de origem é deslocado pelo número de sequência no alfabeto do j-ésima senha, onde j = i% (comprimento da senha). O algoritmo não é o mais ideal ou resistente à análise; no entanto, com um comprimento de senha suficiente, ele fornece alguma confiabilidade.
De acordo com materiais da literatura sobre análise criptográfica, a cifra de Vigenere "embaça" as características das frequências de ocorrência de caracteres no texto, mas permanecem algumas características da aparência de caracteres no texto. A principal desvantagem da cifra Vigenere é que sua chave é repetida. Portanto, uma simples análise criptográfica de uma cifra pode ser construída em dois estágios:
- Pesquisa por tamanho da chave. Você pode analisar a distribuição de frequência em texto criptografado com dizimação diferente. Ou seja, pegue um texto que inclua cada segunda letra do texto cifrado, depois a terceira, etc. Assim que a distribuição de frequência das letras for muito diferente do uniforme (por exemplo, por entropia), podemos falar sobre o comprimento da chave encontrada.
- Criptanálise. Um conjunto de l cifras de César (onde l é o comprimento da chave encontrado), que individualmente são facilmente quebradas.
Os testes de Friedman e Kassiski podem ajudar a determinar o comprimento da chave.
Uma discussão mais detalhada sobre o hack de Vizhener está novamente fora do escopo deste artigo.
Conclusões
Na estrutura deste artigo, um algoritmo foi escrito para escrever um aplicativo que fornece armazenamento e transmissão seguros de arquivos de texto pela rede que requerem acesso compartilhado.
Algumas das decisões expressas acima são controversas ou não são ideais, mas a lógica básica e as coisas fundamentais são indicadas.
github.com/Toxa-p07a1330/encriptedStorage