Google announced this year that it's deprecating some functionality from the BC provider. I am using CMS to encrypt the text using a certificate of X509Certificate type. However, on Android Pie, I encountered NoSuchAlgorithmException as mentioned in the Google post earlier.
For apps targeting Android P or later, the call will throw
NoSuchAlgorithmException.
To resolve this, you should stop specifying a provider and use the
default implementation.
I also checked Conscrypt capabilities here, but couldn't find a way to perform CMS on the text received. Is there any other alternative that can help achieve the same level/type of encryption?
Here's my code:
public byte[] encryptText(String txt) {
CMSEnvelopedDataStreamGenerator cmsdataGen = new CMSEnvelopedDataStreamGenerator();
cmsdataGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(deviceCert).setProvider("BC"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
OutputStream out = cmsdataGen.open(byteArrayOutputStream, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider("BC").build());
byte[] unencryptedContent = txt.getBytes();
out.write(unencryptedContent);
String encrypted = byteArrayOutputStream.toString();
Log.v("Security","ENCRYPTED_STR BEFORE ENCODING= " + encrypted);
out.close();
return byteArrayOutputStream.toByteArray();
}
Related
I use the X509V2AttributeCertificate from Bouncycastle. The problem is, that it is deprecated but I do not find any replacement for the exisiting (deprecated) code. Does someone know the new way?
My way: I use a X509AttributeCertificateHolder, this holder contains the certificate. When I communicate with a client I don't want to send the holder instead I want to send the X509V2AttributeCertificate (for me it seems to be the cleaner code). To do this I use the following code (the client is the file):
X509V2AttributeCertificate certitificate = new X509V2AttributeCertificate(att.getEncoded());
// Store to file (send the byte[] to the client)
String fileName = "test";
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(certitificate.getEncoded());
fos.close();
// Read from file (decode the received certificate)
byte[] readCertificate = Files.readAllBytes(new File(fileName).toPath());
X509V2AttributeCertificate decodedCertificate = new X509V2AttributeCertificate(readCertificate);
How should the X509V2AttributeCertificate be replaced?
I'm pretty new to BouncyCastle and pgp. I've seen many articles and samples on the internet. Almost every encryption sample contains the code snipped below
if (armor)
out = new ArmoredOutputStream(out);
It seems that my local test passed with both armor and none-armor. I googled around but found few useful and the javadoc of ArmoredOutputStream only shows This is basic output stream.
So what's the difference and when to use it?
Complete code sample:
public static void encryptFile(String decryptedFilePath,
String encryptedFilePath,
String encKeyPath,
boolean armor,
boolean withIntegrityCheck)
throws Exception{
OutputStream out = new FileOutputStream(encryptedFilePath);
FileInputStream pubKey = new FileInputStream(encKeyPath);
PGPPublicKey encKey = readPublicKeyFromCollection2(pubKey);
Security.addProvider(new BouncyCastleProvider());
if (armor)
out = new ArmoredOutputStream(out);
// Init encrypted data generator
PGPEncryptedDataGenerator encryptedDataGenerator =
new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, withIntegrityCheck, new SecureRandom(),"BC");
encryptedDataGenerator.addMethod(encKey);
OutputStream encryptedOut = encryptedDataGenerator.open(out, new byte[BUFFER_SIZE]);
// Init compression
PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
OutputStream compressedOut = compressedDataGenerator.open(encryptedOut);
PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
OutputStream literalOut = literalDataGenerator.open(compressedOut, PGPLiteralData.BINARY, decryptedFilePath, new Date(), new byte[BUFFER_SIZE]);
FileInputStream inputFileStream = new FileInputStream(decryptedFilePath);
byte[] buf = new byte[BUFFER_SIZE];
int len;
while((len = inputFileStream.read(buf))>0){
literalOut.write(buf,0,len);
}
literalOut.close();
literalDataGenerator.close();
compressedOut.close();
compressedDataGenerator.close();
encryptedOut.close();
encryptedDataGenerator.close();
inputFileStream.close();
out.close();
}
}
ArmoredOutputStream uses an encoding similar to Base64, so that binary non-printable bytes are converted to something text friendly. You'd do this if you wanted to send the data over email, or post on a site, or some other text medium.
It doesn't make a difference in terms of security. There is a slight expansion of the message size though. The choice really just depends on what you want to do with the output.
ASCII armor is a generic term that means a binary data representation as an ASCII-only text. Technically, there is a lot of ways to ascii-armor binary data, but in the cryptography-related field the PEM format is prevalent (also check this and related questions at serverfault).
The PEM is basically a Base64-encoded binary data wrapped in -----BEGIN SOMETHING----- and -----END SOMETHING----- delimiters and a set of additional headers that can contain some meta information about the binary content.
I have an issue creating a valid CMS signature with Bouncy Castle using a trusted timestamp. The signature creation works well (I want to include the signature to a PDF file), the signature is valid. But after I include a trusted timestamp to the signature's unsigned attribute table, the signature still stays valid, but the Reader reports that The signature includes an embedded timestamp but it is invalid. This leads me to believe, that the hash I timestamp is not the correct one, but I cannot seem to figure out what is the problem with it.
Signing code:
Store store = new JcaCertStore(Arrays.asList(certContainer.getChain()));
CMSSignedDataGenerator signedDataGenerator = new CMSSignedDataGenerator();
JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build());
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA1withRSA");
signedDataGenerator.addSignerInfoGenerator(
infoGeneratorBuilder.build(contentSignerBuilder.build(certContainer.getPrivateKey()), (X509Certificate)certContainer.getSignatureCertificate()));
signedDataGenerator.addCertificates(store);
CMSTypedData cmsData = new CMSProcessableByteArray(data);
signedData = signedDataGenerator.generate(cmsData, false);
Collection<SignerInformation> ss = signedData.getSignerInfos().getSigners();
SignerInformation si = ss.iterator().next(); // get first signer (should be only one)
ASN1EncodableVector timestampVector = new ASN1EncodableVector();
Attribute token = createTSToken(si.getSignature());
timestampVector.add(token);
AttributeTable at = new AttributeTable(timestampVector);
si = SignerInformation.replaceUnsignedAttributes(si, at);
ss.clear();
ss.add(si);
SignerInformationStore newSignerStore = new SignerInformationStore(ss);
CMSSignedData newSignedData = CMSSignedData.replaceSigners(signedData, newSignerStore);
The createTSToken code:
public Attribute createTSToken(byte[] data) throws NoSuchProviderException, NoSuchAlgorithmException, IOException {
// Generate timestamp
MessageDigest digest = MessageDigest.getInstance("SHA1", "BC");
TimeStampResponse response = timestampData(digest.digest(data));
TimeStampToken timestampToken = response.getTimeStampToken();
// Create timestamp attribute
Attribute a = new Attribute(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken, new DERSet(ASN1Primitive.fromByteArray(timestampToken.getEncoded())));
return a;
}
timestampData:
TimeStampRequestGenerator reqgen = new TimeStampRequestGenerator();
TimeStampRequest req = reqgen.generate(TSPAlgorithms.SHA1, data);
byte request[] = req.getEncoded();
URL url = new URL("http://time.certum.pl");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setDoOutput(true);
con.setDoInput(true);
con.setRequestMethod("POST");
con.setRequestProperty("Content-type", "application/timestamp-query");
con.setRequestProperty("Content-length", String.valueOf(request.length));
OutputStream out = con.getOutputStream();
out.write(request);
out.flush();
if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new IOException("Received HTTP error: " + con.getResponseCode() + " - " + con.getResponseMessage());
}
InputStream in = con.getInputStream();
TimeStampResp resp = TimeStampResp.getInstance(new ASN1InputStream(in).readObject());
response = new TimeStampResponse(resp);
response.validate(req);
if(response.getStatus() != 0) {
System.out.println(response.getStatusString());
return null;
}
return response;
Thanks for your help!
Example files:
Signed PDF
Unsigned PDF
Signed PDF with iText
Signed PDF with LTV - edited
signed_lipsum.pdf, first version
The time stamp token references as signer some
CN=e-Szigno Test TSA2,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
which has been issued by
CN=Microsec e-Szigno Test Root CA 2008,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
with serial number 7.
It does not provide this certificate itself, though, and neither is it provided by the encapsulating signature CMS container nor in some validation related information PDF document section.
Thus, at least on my computer there is no chance of verifying the time stamp token in any way and Adobe Reader is completely right not to accept the time stamp.
Have you provided the certificate in question on your computer in a way appropriate for your Adobe Reader? If you have and it still does not work, please supply it for further tests. If you have not, try to retrieve and provide them.
You might want to beef up the time stamp token itself to include that certificate before including it into the signature.
signed_lipsum.pdf, second version
In the updated file signed_lipsum.pdf the signature time stamp contains a TSA certificate, but it is the wrong one!
Just like in the first version the time stamp references a signer certificate with
Subject CN=e-Szigno Test TSA2,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
Issuer CN=Microsec e-Szigno Test Root CA 2008,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
Serial number 7.
The contained certificate, on the other hand, has
Subject CN=e-Szigno Test TSA2,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
Issuer CN=Microsec e-Szigno Test Root CA 2008,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
Serial number 5.
I assume that test TSA uses multiple signing devices / soft-tokens with individual certificates and the OP included the wrong one.
You, therefore, might want to include the correct certificate instead.
BTW, the time stamp in the PDF signed by iText contains a certificate matching the references in the stamp...
RFC 3161 time stamp requests can ask the TSA to include the signer certificate automatically. Bouncy Castle allows to set this flag like this:
TimeStampRequestGenerator reqgen = new TimeStampRequestGenerator();
reqgen.setCertReq(true); // <<<<<<<<<<<<<<<<<<<<<<<<<<
TimeStampRequest req = reqgen.generate(TSPAlgorithms.SHA1, data);
Instead of including the certificate yourself, you might try this.
LTV enabled
From the comments:
Just out of curiosity, what extra needs to be added to make a PDF LTV enabled?
To quote Leonard Rosenthol (PDF guru at Adobe):
LTV enabled means that all information necessary to validate the file (minus root certs) is contained within. So this statement [...] would be true.
the PDF is signed correctly and contains all necessary certificates, a valid CRL or OSCP response for every certificate
(Jan 10, 2013; 7:07pm;Leonard Rosenthol on itext-general)
Need to pass a tokenized (encrypted) username and date from webapp A to webapp B via a url parameter. Something like http://webappB?username=userA×tamp=13445341313 should be sent as http://webappB?token=geufjsggtj26hjdhcjre87klj3. The receiving webapp B should be able to decode the token into the original parameters.
Which technology offers a simple solution that is available for .NET and java? Thanks
I would like to share the solution that I found after some more research.
I chose a very simple symmetric XOR based scramble method in favour of using encryption across platforms.
A piece of code says more than thousand words :
// webappA, encode URL parameter
byte[] userBytes = username.getBytes("UTF-8");
byte[] keyBytes = key.getBytes("UTF-8");
//XOR scramble
byte[] encrypted = new byte[userBytes.length];
for(int i = 0; i < userBytes.length; i++){
encrypted[i] = (byte)(userBytes[i] ^ keyBytes[i % keyBytes.length]);
}
BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(encrypted);
// webappB, decode the parameter
BASE64Decoder decoder = new BASE64Decoder();
byte[] decoded = decoder.decodeBuffer( encoded );
//XOR descramble
byte[] decrypted = new byte[decoded.length];
for(int i = 0; i < decoded.length; i++){
decrypted[i] = (byte)(decoded[i] ^ keyBytes[i % keyBytes.length] );
}
What you've suggested is very simple and doesn't require including technologies outside of what Java and .NET already offer. Simply build up an HTTP request, fire it to the right URL, and listen for the response.
Class References
Java: HttpURLConnectionOracle
.NET: WebRequestMSDN
If you think you'll be doing more of this, or the data gets more frequent, complex, or structured, I highly recommend the ubiquitous SOAPWikipedia protocol for extensibility and modularity.
I'm essentially trying to do the following on a Java/JSP-driven web site:
User supplies a password
Password is used to build a strongly-encrypted archive file (zip, or anything else) containing a text file as well as a number of binary files that are stored on the server. It's essentially a backup of the user's files and settings.
Later, the user can upload the file, provide the original password, and the site will decrypt and unpack the archive, save the extracted binary files to the appropriate folder on the server, and then read the text file so the site can restore the user's old settings and metadata about the binary files.
It's the building/encrypting the archive and then extracting its contents that I'm trying to figure out how to do. I really don't care about the archive format, other than that it is very secure.
The ideal solution to my problem will be very easy to implement, and will require only tried-and-tested libraries with free and nonrestrictive licenses (e.g. apache, berkeley, lgpl).
I'm aware of the TrueZIP and WinZipAES libraries; the former seems like massive overkill and I can't tell how stable the latter is... Are there other solutions out there that would work well?
If you know how to create a zip file using the java.util.zip package, you can create a PBE Cipher and pass that to a CipherOutputStream or a CipherInputStream (depending on if you're reading or writing).
The following should get you started:
public class ZipTest {
public static void main(String [] args) throws Exception {
String password = "password";
write(password);
read(password);
}
private static void write(String password) throws Exception {
OutputStream target = new FileOutputStream("out.zip");
target = new CipherOutputStream(target, createCipher(Cipher.ENCRYPT_MODE, password));
ZipOutputStream output = new ZipOutputStream(target);
ZipEntry e = new ZipEntry("filename");
output.putNextEntry(e);
output.write("helloWorld".getBytes());
output.closeEntry();
e = new ZipEntry("filename1");
output.putNextEntry(e);
output.write("helloWorld1".getBytes());
output.closeEntry();
output.finish();
output.flush();
}
private static Cipher createCipher(int mode, String password) throws Exception {
String alg = "PBEWithSHA1AndDESede"; //BouncyCastle has better algorithms
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(alg);
SecretKey secretKey = keyFactory.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("PBEWithSHA1AndDESede");
cipher.init(mode, secretKey, new PBEParameterSpec("saltsalt".getBytes(), 2000));
return cipher;
}
private static void read(String password) throws Exception {
InputStream target = new FileInputStream("out.zip");
target = new CipherInputStream(target, createCipher(Cipher.DECRYPT_MODE, password));
ZipInputStream input = new ZipInputStream(target);
ZipEntry entry = input.getNextEntry();
while (entry != null) {
System.out.println("Entry: "+entry.getName());
System.out.println("Contents: "+toString(input));
input.closeEntry();
entry = input.getNextEntry();
}
}
private static String toString(InputStream input) throws Exception {
byte [] data = new byte[1024];
StringBuilder result = new StringBuilder();
int bytesRead = input.read(data);
while (bytesRead != -1) {
result.append(new String(data, 0, bytesRead));
bytesRead = input.read(data);
}
return result.toString();
}
}
The answer is already given (use a cipher as Kevin pointed out), so I am only doing a suggestion about an important matter which seems to be missing in your topicstart: ensure that you're using HTTPS instead of HTTP. Otherwise one with a network sniffer would be able to get the user-supplied password from the packets. How to do it depends on the appserver in question. Best is to refer its documentation. If it is for example Apache Tomcat, then you can find everything in the Tomcat SSL HOW-TO.
Hope this helps.
Though it may not be specific to your query I wonder if truecrypt could be of use. Your webserver could create an encrypted container into which the zip file would be copied. The encrypted container could then be downloaded. Potentially a little messy however the encryption should be strong and the downloaded image could be mounted on a variety of operating systems.
There are surely a few suggestions here on how to solve your problem, but I'm missing a very big BUT in the responses. You cannot fulfill both "password based" and "strong encryption" for any reasonable definition of "strong encryption".