Arquivo

Archive for the ‘Programação’ Category

Criptografia Simétrica com Cipher

O Cipher oferece métodos de criptografia para encriptação e decriptação. Essa classe faz parte do core do JCE (Java Cryptographic Extension). Para utilizar criptografia AES, você deve atualizar a política de jurisdição do JCE de acordo com a sua versão do Java. Como você verá no exemplo abaixo, o digest é apenas uma parte do processo de criptografia.

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class SimpleCipher {
   public String encrypt(String value, String key) 
      throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, 
             InvalidAlgorithmParameterException, IllegalBlockSizeException, 
             BadPaddingException, UnsupportedEncodingException {
      Cipher cipher = config(Cipher.ENCRYPT_MODE, key);
      byte[] encryptedResult = cipher.doFinal(value.getBytes());
      return DatatypeConverter.printBase64Binary(encryptedResult);
   }

   public String decrypt(String value, String key) 
      throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
             InvalidAlgorithmParameterException, IllegalBlockSizeException,
             BadPaddingException, UnsupportedEncodingException {
      Cipher cipher = config(Cipher.DECRYPT_MODE, key);
      byte[] decryptedResult = cipher.doFinal(DatatypeConverter.parseBase64Binary(value));
      return new String(decryptedResult);
   }

   private Cipher config(int cipherMode, String key) 
      throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
             InvalidAlgorithmParameterException, UnsupportedEncodingException {
      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
      cipher.init(cipherMode, createKey(key), createIv());
      return cipher;
   }

   private Key createKey(String key)
      throws NoSuchAlgorithmException, UnsupportedEncodingException {
      MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
      byte[] secretKey = messageDigest.digest(key.getBytes("UTF-8"));
      return new SecretKeySpec(secretKey, "AES");
   }
	
   private AlgorithmParameterSpec createIv() throws UnsupportedEncodingException {
      return new IvParameterSpec("0000000000000000".getBytes("UTF-8"));
   }
}

O teste abaixo valida a implementação exposta:

import org.junit.Assert;
import org.junit.Test;
public class SimpleCipherTest {
   @Test
   public void testeEncryptDecryptParametros() throws Exception{
      SimpleCipher cipher = new SimpleCipher();
      String encrypt = cipher.encrypt("Um simples teste", "123456");
      String decrypt = cipher.decrypt(encrypt, "123456");
      Assert.assertEquals("FALHOU", "Um simples teste", decrypt);
   }
}

Utilizei o OpenSSL para reproduzir o mesmo efeito produzido pelo código java, mas o resultado gerado é diferente:

$echo -n "Um simples teste" | openssl enc -A -aes-256-cbc 
-base64 -K "123456" -iv "0000000000000000"

A explicação é que AES/ECB/PKCS5Padding não é o mesmo que aes-256-cbc. Se a chave gerada no java tiver apenas 16 bytes, deveremos utilizar o algoritmo aes-128-ecb do OpenSSL.

Referências

1. [https://stackoverflow.com/questions/7615743/java-aes-without-padding]
2. [https://pt.stackoverflow.com/questions/145385/como-usar-o-metodo-de-encripta%C3%A7%C3%A3o-openssl-encrypt]
3. [https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html]
4. [https://github.com/EliteAndroidApps/WhatsApp-Crypt12-Decrypter/issues/3]
5. [https://stackoverflow.com/questions/23597984/aes-256-encryption-issue]
6. [https://8gwifi.org/CipherFunctions.jsp]
7. [https://stackoverflow.com/questions/32018672/encrypt-using-openssl-in-the-same-way-java-does]

Anúncios

Customizando o Componente DocumentViewer para Apresentar Documentos PDF

O DocumentViewer é um componente do Primefaces Extensions que encapsula o PDF.js, um script que renderiza arquivos PDF no navegador.

Encontrei esse componente durante minhas pesquisas sobre a conversão de arquivos PDF para imagens. Depois que fiz alguns testes com ele, achei o resultado bem satisfatório, pois além de manter a qualidade do arquivo original, ele permite várias ações sobre o arquivo visualizado que poderiam ser customizadas via javascript – no meu caso, era necessário remover principalmente as ações de download, impressão e uma forma de impedir que o usuário copiasse com o mouse o conteúdo apresentado. Coloquei o componente dentro de uma dialog:

<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:p="http://primefaces.org/ui"
	xmlns:pe="http://primefaces.org/ui/extensions">
(...)
 <p:dialog height="100%" width="100%" resizable="false" 
   draggable="false" showHeader="false" modal="true">
   <f:view contentType="text/html" locale="pt_BR" >
      <pe:documentViewer id="viewer" zoom="page-width" height="500" locale="pt_BR" 
         value="#{visualizadorMB.content}" download="arquivo.pdf" cache="false"/>
   </f:view>
   <p:remoteCommand oncomplete="block();" autoRun="true" />
 </p:dialog>
(...)
</html>

Nessa versão simplificada do ManagedBean, carreguei o arquivo em um StreamedContent. Lá no XHTML, utilizei um remoteCommand para chamar o javascript, mas como não havia garantias de que os componentes que eu iria esconder já estavam visíveis, utilizei a função timeout do Javascript. Realmente tive problemas no Google Chrome, pois algumas vezes os botões de impressão, download e outros ficavam visíveis na dialog:

@ManagedBean 
public class VisualizadorMB{
   private StreamedContent content;
   public ViewerMB() {
      try {
         File file = new File("{PATH_TO_FILE}");
         FileInputStream in = new FileInputStream(file);
         content = new DefaultStreamedContent(in);
      } catch (Exception e) {
           e.printStackTrace();
      }
   }
}

Agora entra a parte que comentei sobre a customização do componente com Javascript. Basicamente, escondi botões e removi listeners à partir de um jQuery.fn.extend.

$(document).ready(function() {
	jQuery.fn.extend({
		disableCopy : function() {
			return this.each(function() {
				this.onkeydown = function(event) {
					if (event == undefined) {
						event = window.event;
					}
					if (event.ctrlKey
							&& (event.keyCode == 65 || event.keyCode == 67 || event.keyCode == 96 
									|| event.keyCode == 97 || event.keyCode == 80 || event.keyCode == 83)) {
						event.preventDefault();
						event.stopPropagation();
					}
				};
			});
		}
	});
   $(this).disableCopy();
}

A segunda parte do script atua especificamente no iframe criado pelo componente.

function block(){
    var acao = function () {
    	$( "iframe" ).each(function( index ) {
    		jQuery.fn.extend({
    			disableSelection : function() {
    				return this.each(function() {
    					this.onselectstart = function() {
    						return false;
    					};
    					this.unselectable = "on";
    					jQuery(this).css('user-select', 'none');
    					jQuery(this).css('-o-user-select', 'none');
    					jQuery(this).css('-moz-user-select', 'none');
    					jQuery(this).css('-khtml-user-select', 'none');
    					jQuery(this).css('-webkit-user-select', 'none');
    				});
    			},
    			//https://stackoverflow.com/questions/9251837/how-to-remove-all-listeners-in-an-element
    			disableClick : function() {
    				return this.each(function() {
    					this.style.display = "none";
    					var old_element = this;
    					var new_element = old_element.cloneNode(true);
    					old_element.parentNode.replaceChild(new_element, old_element);
    				});
    			},
    			disableSelectedAll : function() {
    				return this.each(function() {
    					this.onkeydown = function(event) {
    						if (event == undefined) {
    							event = window.event;
    						}
    						//https://css-tricks.com/snippets/javascript/javascript-keycodes/
    						//CTRL = keyCode = 17, ou event.ctrlKey = 'true'
    						//CTRL + 'a' = 65
    						//CTRL + 'c' = 67
    						//CTRL + 'p' = 80
    						//CTRL + 's' = 83
    						//CTRL + 'numpad0' = 96
    						//CTRL + 'numpad1' = 97
    						if (event.ctrlKey
    								&& (event.keyCode == 65 || event.keyCode == 67 || event.keyCode == 96 
    										|| event.keyCode == 97 || event.keyCode == 80 || event.keyCode == 83)) {
    							event.preventDefault();
    							event.stopPropagation();
    							/*
    							 * O Chrome nao permite desabilitar o atalho da impressao
    							 * 
    							 * @see https://stackoverflow.com/questions/30331771/disable-print-preview-in-chrome
    							 */
    						}
    					};
    				});
    			}
    		});
    		var id = $( this ).attr('id');
    		// Pode haver outros iframes na tela que nao sao o visualizador de PDF
    		if(id.search("pdfViewer") != -1) {
    			//var iframe = document.getElementById($('[id$=pdfViewer]').attr('id'));
    			var iframe = document.getElementById(id);
    			if(iframe){
    				$('#toolbarViewerRight', iframe.contentWindow.document).disableClick();
    				$('#viewFind', iframe.contentWindow.document).disableClick();
    				$('#toolbarSidebar', iframe.contentWindow.document).disableClick();
    				
    				iframe.contentWindow.document.oncontextmenu = function(){return false;};

    				$(iframe.contentWindow.document).disableSelection();
    				$(iframe.contentWindow.document).disableSelectedAll();
    				$('#viewerContainer', iframe.contentWindow.document).disableSelectedAll();
    				$('#viewer', iframe.contentWindow.document).disableSelectedAll();
    			}
    		}
    		blockPDFViewer();
    	}); 
    };
    setTimeout(acao, 200);
}

Esse foi o máximo que consegui restringir das ações do usuário. O atalho de impressão, [CTRL]+[P], não pode ser bloqueado no Google Chrome. Nesse fórum, explicaram que o Google e outros fabricantes, privilegiando uma experiência consistente do usuário, impedem que os programadores intervenham em certos comportamentos das páginas. Mesmo assim, pense só: é impossível bloquear todas as ações de cópia programaticamente. Um antigo colega me disse que “quanto mais espertos tentamos ser, mais burros ficamos”. Um antigo chefe disse que “independente de você tirar o cérebro e colocar do lado para tentar enxergar as coisas do ponto de vista do usuário, ele sempre será capaz de fazer coisas que você não previu”. Sendo assim, pense comigo: como o conteúdo de uma página web pode ser copiado por um usuário? O usuário sempre pode salvar e imprimir a página inteira, utilizando os menus da barra de ferramentas do navegador, pode instalar plugins no navegador que permitam baixar o conteúdo no formato desejado, pode copiar o conteúdo com ferramentas de depuração do próprio navegador e reconstruir o documento inteiro ou simplesmente pode fotografar a tela com seu celular.

Categorias:Programação

Verificando a Data de Modificação de um Arquivo no Bash

O comando stat exibe informações de um arquivo ou de um diretório. Esse comando é bem mais útil do que o comando ls para esse propósito. O comando date exibe a data atual do sistema operacional e também pode ser utilizado para formatar uma data de acordo com um determinado padrão. Nesse artigo, veremos como utilizar esses dois comandos para verificar a data de atualização de um arquivo.

   # Obtem a data de modificação do arquivo em formato 
   # legível (2018-05-01 01:00:00.976116935 +1100)
   FILE=$(stat -c %y "arquivo.txt" );

   # Obtem o trecho da string de data referente ao ano, mês e dia
   FILE=${FILE:0:10};

   # Obtem a data de atualização do arquivo em segundos
   FILE=$(date --date=$FILE +%s);

   DATA="2018-05-01";

   # Obtem a data informada em segundos
   DATA=$(date --date=$DATA +%s);
   
   if [ $(($DATA-$FILE)) -lt 0 ]; then 
      # Faz alguma coisa...
   fi

Se seus script foi escrito no Windows, ele apresentará caracteres não reconhecíveis pelo bash. Você deve executar o comando abaixo para limpar o arquivo:

sed -i -e 's/\r$//' seu_script.sh

Referências

1. [https://stackoverflow.com/questions/1207052/how-to-use-shell-script-checking-last-changed-time-of-a-file]
2. [https://www.linuxquestions.org/questions/programming-9/get-file-modification-date-time-in-bash-script-163731/]
3. [https://unix.stackexchange.com/questions/187966/calculate-date-difference-between-last-modified-date-of-a-file-and-now-using-she]
4. [http://chriscase.cc/2013/05/getting-the-creation-timestamp-of-a-file-with-stat/]

Categorias:Programação

Views Materializadas

Uma view é uma consulta que retorna um conjunto de dados de uma ou mais tabelas e pode ser acessada por nome. A view sempre representa o estado atual do dado acessado, pois sempre que é acionada ela regera seu conjunto de dados.

CREATE VIEW VW_PESSOA (CPF, NOME)
AS 
SELECT PESSOA.CPF,PESSOA.NOME
FROM  TBL_PESSOA PESSOA;

Uma view materializada tem o mesmo objetivo da view, mas quando ela é criada o resultado de sua consulta é armazenado em banco em uma estrutura tabular.

CREATE MATERIALIZED VIEW MVW_PESSOA (CPF, NOME)
AS 
SELECT PESSOA.CPF,PESSOA.NOME
FROM  TBL_PESSOA PESSOA;

A vantagem dessa abordagem é que o dado acessado sempre estará disponível, pois a consulta não será reexecutada quando a view materializada for chamada. Devido ao seu caráter estático, é necessário estabelecer um processo para atualizá-la. Pode-se, por exemplo, utilizar uma trigger ou configurar a crontab.

Tanto a view quanto a view materializada, devido ao comportamento tabular que apresentam, podem ser mapeadas como tabelas na sua aplicação:

@Table(name = "MVW_PESSOA")
public class PessoaView{
   @Id
   @Column(name = "CPF")
   private Long cpf;

   @Column(name = "NOME")
   private String nome;
}

Alternativas para Conversão de PDF em Imagens

Há alguns anos, me deparei com o problema de converter PDFs para imagens em uma aplicação. Fiz estudos com algumas bibliotecas Java, mas não gostei muito do resultado. As bibliotecas gratuitas em geral produziam imagens com qualidade baixa e às vezes sequer convertiam o arquivo PDF em função de algum recurso inerente ao arquivo e que era desconhecido para a ferramenta. Mesmo assim, os maiores problemas que encontrei eram referentes à performance e ao uso de memória, pois a conversão era feita pela aplicação Java. Inicialmente pensei em uma máquina dedica para a conversão dos arquivos, mas deixei essa ideia em stand by enquanto pesquisava alguma alternativa, pois às vezes o aumento de recursos físicos ataca o efeito colateral de problemas produzidos por nossa própria implementação.

Um colega indicou o Ghostscript, que é escrito em C e roda como um processo no sistema operacional. Fizemos uma implementação multithread em Java que gravava o arquivo PDF em disco, delegava ao Ghostscript – e para o sistema operacional – a tarefa de converter o arquivo para imagem e depois coletava os resultados. Depois de alguns tunnings, as imagens ficaram com qualidade próxima à original.

Nesse artigo, vou compartilhar os códigos de exemplo que produzi para entender como funcionavam algumas das bibliotecas de conversão de arquivos que estudei antes e depois da implementação do Ghostscript.

PDFBox

O PDFBox da Apache foi a primeira biblioteca que testei. Ele já era bem conhecido e maduro.

<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.7.1</version>
</dependency>
		
<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-log4j12</artifactId>
   <version>1.7.1</version>
</dependency>
		
<dependency>
   <groupId>org.apache.pdfbox</groupId>
   <artifactId>pdfbox</artifactId>
   <version>1.8.3<
</dependency>

A implementação ficou assim:

public class PDFBoxPdfToImage {
   public static void main(String[] args) throws Exception{
      String sourceDir = "C:/tmp/teste.pdf";
      String destinationDir = "C:/tmp/";
      File sourceFile = new File(sourceDir);
      PDDocument document = PDDocument.load(sourceDir);
      @SuppressWarnings("unchecked")
      List<PDPage> list = document.getDocumentCatalog().getAllPages();
      sourceFile.getName().replace(".pdf", "");
      int pageNumber = 1;
      for (PDPage page : list) {
         BufferedImage image = page.convertToImage(BufferedImage.TYPE_INT_RGB, 100);
         File outputfile = new File(destinationDir + pageNumber + ".jpeg");
         ImageIO.write(image, "png", outputfile);
         pageNumber++;
      }
      document.close();
   }
}

Nesse exemplo, converti um arquivo PDF qualquer, página à página, com resolução de 100dpi. Para arquivos PDF simples, a qualidade final da imagem até que ficou razoável, mas muitos PDFs mais complexos, cheios de figuras e camadas, sequer foram convertidos: a biblioteca gerou páginas pretas.

jPDFImages

O jPDFImages é uma biblioteca paga da Qoppa Software. Você pode baixar o jar trial deles.

import java.io.File;
import com.qoppa.pdfImages.PDFImages;

public class PDFToImage {
   public static void main(String[] args) throws Exception {
      String sourceDir = "C:/tmp/teste.pdf";
      String destinationDir = "C:/tmp/";
      File base = new File(destinationDir + "/");
      if (!base.exists()) {
         base.mkdir();
      }
      PDFImages pdfDoc = new PDFImages(sourceDir, null);
      for (int count = 0; count < pdfDoc.getPageCount(); ++count) {
         pdfDoc.savePageAsJPEG(count, destinationDir + "/" + count + ".jpg", 100, 0.9f);
      }
   }
}

Nesse exemplo, converti um arquivo PDF qualquer, página à página, com resolução de 100dpi e 90% de qualidade. Qualquer arquivo que tentei converter, até os mais complexos, ficaram com qualidade excelente.

Ghost4J

O Ghost4J fornece uma API que abstrai o uso do Ghostscript. O Ghostscript deve estar instalado e rodando para que o Ghost4J funcione.

<dependency>
   <groupId>org.ghost4j</groupId>
   <artifactId>ghost4j</artifactId>
   <version>1.0.0</version>
</dependency>

No primeiro exemplo abaixo, configuro o Ghost4J para utilizar explicitamente as configurações do Ghostscript para conversão e gravar as imagens no diretório C:/tmp.

import java.awt.image.RenderedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import org.ghost4j.Ghostscript;
import org.ghost4j.GhostscriptException;

public class Ghost4jPDFToImage {
   public static void main(String[] args) throws Exception {
       Ghostscript gs = Ghostscript.getInstance();
       synchronized (gs) {
          List<String> gsArgs = new ArrayList<String>();
          gsArgs.add("gswin64c");
          gsArgs.add("-dNOPAUSE");
          gsArgs.add("-dBATCH");
          gsArgs.add("-dSAFER");
          gsArgs.add("-dGraphicsAlphaBits=4");
          gsArgs.add("-dTextAlphaBits=4");
          gsArgs.add("-dDOINTERPOLATE");
          gsArgs.add("-sDEVICE=display");
          gsArgs.add("-sDisplayHandle=0");
          gsArgs.add("-dDisplayFormat=16#804");
          gsArgs.add("-r100");
          gsArgs.add("-sOutputFile=C:/tmp/%0000d.jpeg");
          gsArgs.add("C:/tmp/teste.pdf");
          gs.initialize(gsArgs.toArray(new String[gsArgs.size()]));
          gs.exit();
      }
      try {
         Ghostscript.deleteInstance();
      } catch (GhostscriptException e) {
         System.out.println("ERROR: " + e.getMessage());
      }
   }
}

Esse segundo exemplo também utiliza explicitamente as configurações do Ghostscript, mas adicionei também um um ImageWriterDisplayCallback para delegar para a aplicação o controle dos arquivos de imagem gerados.

import java.awt.image.RenderedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import org.ghost4j.Ghostscript;
import org.ghost4j.GhostscriptException;
import org.ghost4j.display.ImageWriterDisplayCallback;

public class Ghost4jPDFToImage {
   public static void main(String[] args) throws Exception {
      Ghostscript gs = Ghostscript.getInstance();
      ImageWriterDisplayCallback displayCallback = new ImageWriterDisplayCallback();
      gs.setDisplayCallback(displayCallback);
      synchronized (gs) {
         List<String> gsArgs = new ArrayList<String>();
         gsArgs.add("gswin64c");
         gsArgs.add("-dNOPAUSE");
         gsArgs.add("-dBATCH");
         gsArgs.add("-dSAFER");
         gsArgs.add("-dGraphicsAlphaBits=4");
         gsArgs.add("-dTextAlphaBits=4");
         gsArgs.add("-dDOINTERPOLATE");
         gsArgs.add("-sDEVICE=display");
         gsArgs.add("-sDisplayHandle=0");
         gsArgs.add("-dDisplayFormat=16#804");
         gsArgs.add("-r100");
         gsArgs.add("-sOutputFile=C:/tmp/%0000d.jpeg");
         gsArgs.add("C:/tmp/teste.pdf");
         gs.initialize(gsArgs.toArray(new String[gsArgs.size()]));
         gs.exit();
      }
      for (int i = 0; i < displayCallback.getImages().size(); i++) {
         ImageIO.write((RenderedImage) displayCallback.getImages().get(i), "jpeg",
               new File("C:/tmp/" + i + ".jpeg"));
      }
      try {
         Ghostscript.deleteInstance();
      } catch (GhostscriptException e) {
         System.out.println("ERROR: " + e.getMessage());
      }
   }
}

Essa última implementação é mais simples e utiliza, implicitamente, o Ghostscript instalado na máquina:

import java.awt.Image;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import org.ghost4j.Ghostscript;
import org.ghost4j.GhostscriptException;
import org.ghost4j.document.PDFDocument;
import org.ghost4j.renderer.SimpleRenderer;

public class Ghost4jPDFToImage {
   public static void main(String[] args) throws Exception{
      PDFDocument document = new PDFDocument();
      document.load(new File("C:/tmp/teste.pdf"));
      SimpleRenderer renderer = new SimpleRenderer();
      renderer.setResolution(100);
      renderer.setAntialiasing(SimpleRenderer.OPTION_ANTIALIASING_HIGH );
      List<Image> images = renderer.render(document);
      try {
         for (int i = 0; i < images.size(); i++) {
            ImageIO.write((RenderedImage) images.get(i), "png", new File("C:/tmp/" + (i + 1) + ".png"));
	 }
      } catch (IOException e) {
         System.out.println("ERROR: " + e.getMessage());
      }
   }
}

Você poderia modificar essa implementação para utilizar várias Threads para conversão de imagens conforme a documentação oficial.

Categorias:Programação

Como Publicar Artefatos Automaticamente no Nexus

Em outro artigo, expliquei a exceção, que é incluir um jar sem pom ao Maven. Em um artigo mais recente, mostrei uma forma um pouco melhor, que é fazendo o deploy daquele jar no repositório local. Nesse artigo, vou descrever um caminho bem melhor, que é utilizando o Nexus, uma ferramenta que permite o gerenciamento centralizado dos artefatos dos nossos projetos.

Primeiro, você deve adicionar as configurações específicas do Nexus no seu arquivo de configurações do Maven, que fica lá no diretório onde ele está instalado. Basicamente, você deve configurar um mirror e um profile.

<settings>
  <mirrors>
    <mirror>
      <id>nexus</id>
      <mirrorOf>*</mirrorOf>
      <url>http://10.100.0.1:8081/nexus/content/groups/public</url>
    </mirror>
  </mirrors>
  <profiles>
    <profile>
      <id>nexus</id>
      <!--Enable snapshots for the built in central repo to direct -->
      <!--all requests to nexus via the mirror -->
      <repositories>
        <repository>
          <id>central</id>
          <url>http://central</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </repository>
      </repositories>
     <pluginRepositories>
        <pluginRepository>
          <id>central</id>
          <url>http://central</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>
  <activeProfiles>
    <!--make the profile active all the time -->
    <activeProfile>nexus</activeProfile>
  </activeProfiles>
   <servers>
    <server>
      <id>deployment</id>
      <username>deployment</username>
      <password>deployment</password>
    </server>
    <server>
      <id>releases</id>
      <username>admin</username>
      <password>admin</password>
    </server>
  </servers>
</settings>

Agora vamos criar um projeto de teste. Adicione ao POM desse projeto, na seção distributionManagement, as informações do repositório do Nexus onde sua dependência será implantada.

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>br.com.tst</groupId>
  <artifactId>teste</artifactId>
  <packaging>pom</packaging>
  <version>0.0.1</version>

  <modules>
  	<module>teste-module</module>
  </modules>
  
  <distributionManagement>
    <repository>
      <id>releases</id>
      <url>http://10.100.0.1:8081/nexus/content/repositories/releases</url>
    </repository>
  </distributionManagement>
</project>

Crie um módulo para o seu projeto. Isso é apenas para deixar a estrutura mais interessante. Em um projeto real, você provavelmente trabalhará com módulos para melhor organização:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>br.com.tst</groupId>
    <artifactId>teste</artifactId>
    <version>0.0.1</version>
  </parent>
  <artifactId>teste-module</artifactId>
  <packaging>jar</packaging>
</project>

Rode o comando abaixo para implantar o artefato no Nexus:

mvn deploy

Você também pode rodar o comando abaixo para instalar seu pacote no repositório local do maven:

mvn install

Lá no Nexus, uma estrutura assim deve ter sido criada no repositório releases:

Finalmente, para utilizar essa dependência em outro projeto, basta referenciá-la normalmente no POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <dependencies>
  (...)
     <dependency>
       <groupId>br.com.tst</groupId>
       <artifactId>teste</artifactId>
       <version>0.0.1</version>
     </dependency>
  (...)
  </dependencies>
</project>
Categorias:Programação Tags:, , ,

Assinando E-mail Digitalmente com o Bouncy Castle

O Bouncy Castle é uma coleção de APIs usadas em criptografia para Java. O código abaixo é um exemplo de como assinar um e-mail digitalmente com essa API.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
import org.bouncycastle.asn1.smime.SMIMECapability;
import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.util.Store;

public class MailSigner {

   private static final String BOUNCE_CASTLE_PROVIDER = "BC";
   private static final String CRYPT_FILE_FORMAT = "PKCS12";
   private static final String CRYPT_ALGORITH = "RSA";
   private static final String SHA1_PKCS_PROVIDER = "SHA1withRSA";
   private static final String MD5_PKCS_PROVIDER = "MD5withRSA";

   private SMIMECapabilityVector capabilities;

   public MailSigner() {
      load();
   }

   private void load() {
      capabilities = new SMIMECapabilityVector();
      capabilities.addCapability(SMIMECapability.dES_EDE3_CBC);
      capabilities.addCapability(SMIMECapability.rC2_CBC, 128);
      capabilities.addCapability(SMIMECapability.dES_CBC);
      /* Adiciona o provider do BouncyCastle */
      Security.addProvider(new BouncyCastleProvider());
   }

   public Message sign(MimeMessage message) throws Exception {
      String password = "SENHA_CERTIFICADO";
      KeyStore keystore = loadKeyStore(password);
      String alias = findAlias(keystore, password);
      if (alias == null) {
         throw new UnrecoverableKeyException("cannot find alias for certificate");
      }

      PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, password.toCharArray());
      Certificate certificate = keystore.getCertificate(alias);
      SMIMESignedGenerator signer = configSigner(certificate, privateKey);
      MimeMessage signedMessage = new MimeMessage(message.getSession());

		/*
		 * Copia todos os MIME header da mensagem original para a mensagem assinada
		 */
		copyHeader(message, signedMessage);

		/* Assina a mensagem */
		MimeMultipart mimeMultipart = signer.generate(message, BOUNCE_CASTLE_PROVIDER);

		/* Insere o conteudo da mensagem original na mensagem assinada */
		signedMessage.setContent(mimeMultipart);
		signedMessage.saveChanges();

		return signedMessage;
	}


	@SuppressWarnings("rawtypes")
	private void copyHeader(MimeMessage message, MimeMessage signedMessage) throws MessagingException {
		Enumeration headers = message.getAllHeaderLines();
		while (headers.hasMoreElements()) {
			signedMessage.addHeaderLine((String) headers.nextElement());
		}
	}

	private String findAlias(KeyStore keystore, String password) throws KeyStoreException, UnrecoverableKeyException,
			NoSuchAlgorithmException {
		Enumeration<String> aliases = keystore.aliases();
		while (aliases.hasMoreElements()) {
			String alias = (String) aliases.nextElement();
			/* Obtem a chave privada do certificado */
			PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, password.toCharArray());
			if (privateKey != null) {
			   return alias;
			}
		}
		return null;
	}

	private KeyStore loadKeyStore(String password) throws KeyStoreException, NoSuchProviderException,
			FileNotFoundException, IOException, NoSuchAlgorithmException, CertificateException {
		File certFile = new File("CAMINHO_ARQUIVO_PFX_ASSINATURA");
		FileInputStream fis = new FileInputStream(certFile);

		/* Carrega o keystore */
		KeyStore keystore = KeyStore.getInstance(CRYPT_FILE_FORMAT, BOUNCE_CASTLE_PROVIDER);
		keystore.load(fis, password.toCharArray());

		return keystore;
	}

	private SMIMESignedGenerator configSigner(Certificate certificate, PrivateKey privateKey)
			throws OperatorCreationException, CertificateEncodingException {
		ASN1EncodableVector attributes = new ASN1EncodableVector();
		attributes.add(new SMIMEEncryptionKeyPreferenceAttribute(new IssuerAndSerialNumber(new X500Name(
				((X509Certificate) certificate).getIssuerDN().getName()), ((X509Certificate) certificate)
				.getSerialNumber())));
		attributes.add(new SMIMECapabilitiesAttribute(capabilities));

		JcaSimpleSignerInfoGeneratorBuilder signerInfoBuilder = new JcaSimpleSignerInfoGeneratorBuilder();
		signerInfoBuilder.setProvider(BOUNCE_CASTLE_PROVIDER);
		signerInfoBuilder.setSignedAttributeGenerator(new AttributeTable(attributes));
		SignerInfoGenerator signerInfoGenerator = signerInfoBuilder.build(
				CRYPT_ALGORITH.equals(privateKey.getAlgorithm()) ? SHA1_PKCS_PROVIDER : MD5_PKCS_PROVIDER, privateKey,
				(X509Certificate) certificate);

		SMIMESignedGenerator signer = new SMIMESignedGenerator();
		signer.addSignerInfoGenerator(signerInfoGenerator);

		/* Adiciona a lista de certificados no Bouncy Castle */
		List<Certificate> certList = new ArrayList<Certificate>();
		certList.add(certificate);
		Store certs = new JcaCertStore(certList);
		signer.addCertificates(certs);
		return signer;
	}
}

Referências

1. [http://www.docjar.org/src/api/org/bouncycastle/mail/smime/examples/CreateSignedMail.java]
2. [http://grepcode.com/file/repo1.maven.org/maven2/org.bouncycastle/bcmail-jdk16/1.46/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java#SendSignedAndEncryptedMail.main%28java.lang.String%5B%5D%29]
3. [http://www.thehecklers.org/tag/bouncycastle/]
4. [http://www.thehecklers.org/2013/01/14/secure-email-from-java/]
5. [http://www.docjar.org/html/api/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java.html]
6. [http://answer.techwikihow.com/1060825/encrypted-smime-issue.html]
7. [http://fastpicket.com/blog/2012/05/14/easy-pgp-in-java-bouncy-castle/]