I have created a SSL certificate using acme client acme4j: https://github.com/shred/acme4j.
But while I'm generating a self signed certificate I'm facing an exception while parsing it. Here is the my generated certificate:
{Version: V3
Subject: T=spid: yuz8xxz
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
Key: Sun RSA public key, 2048 bits
modulus:
public exponent: 65537
Validity: [From: Mon Apr 10 17:56:36 IST 2017,
To: Mon Apr 17 17:56:36 IST 2017]
Issuer: T=spid: yuz8xxz
SerialNumber: [ 015b57d4 807c]
Certificate Extensions: 1
[1]: ObjectId: 1.3.6.1.5.5.7.1.26 Criticality=false
**Extension unknown: DER encoded OCTET string =**
0000: 04 23 30 21 A0 12 16 07 79 75 7A 38 78 78 7A 16 .#0!....yuz8xxz.
0010: 07 79 75 7A 38 78 78 7A A1 0B 16 06 31 32 33 34 .yuz8xxz....1234
0020: 35 36 02 01 01 56...
]
Algorithm: [SHA256withRSA]
Signature:
]
}
Acme4j code uses a java.security.cert.X509Certificate class. The toString() method of this class (when using Sun's default provider) is generating this "extension unknown" output (according to the corresponding code).
So, in order to parse it correctly (get a formatted output), you'll probably have to change acme4j's code (or write your own), including the code to parse this extension.
In my tests (Java 7 and BouncyCastle 1.56), I created a wrapper to X509Certificate and created a format method based on BouncyCastle's code (I copied the most part and just added code for TNAuthorizationList extension). The code below is not optimal (poor exception handling and some deprecated classes), but you can get an idea.
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.misc.*;
import org.bouncycastle.asn1.util.ASN1Dump;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.util.encoders.Hex;
public class CertificateWrapper {
private X509Certificate cert;
public CertificateWrapper(X509Certificate cert) {
this.cert = cert;
}
public String format() throws Exception {
StringBuffer buf = new StringBuffer();
String nl = System.getProperty("line.separator");
buf.append(" [0] Version: ").append(this.cert.getVersion()).append(nl);
buf.append(" SerialNumber: ").append(this.cert.getSerialNumber()).append(nl);
buf.append(" IssuerDN: ").append(this.cert.getIssuerDN().toString()).append(nl);
buf.append(" Start Date: ").append(this.cert.getNotBefore()).append(nl);
buf.append(" Final Date: ").append(this.cert.getNotAfter()).append(nl);
buf.append(" SubjectDN: ").append(this.cert.getSubjectDN().toString()).append(nl);
buf.append(" Public Key: ").append(this.cert.getPublicKey()).append(nl);
buf.append(" Signature Algorithm: ").append(this.cert.getSigAlgName()).append(nl);
byte[] sig = this.cert.getSignature();
buf.append(" Signature: ").append(new String(Hex.encode(sig, 0, 20))).append(nl);
for (int i = 20; i < sig.length; i += 20) {
if (i < sig.length - 20) {
buf.append(" ").append(new String(Hex.encode(sig, i, 20))).append(nl);
} else {
buf.append(" ").append(new String(Hex.encode(sig, i, sig.length - i))).append(nl);
}
}
TBSCertificateStructure tbs = TBSCertificateStructure.getInstance(ASN1Sequence.fromByteArray(cert.getTBSCertificate()));
X509Extensions extensions = tbs.getExtensions();
if (extensions != null) {
Enumeration e = extensions.oids();
if (e.hasMoreElements()) {
buf.append(" Extensions: \n");
}
while (e.hasMoreElements()) {
ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
X509Extension ext = extensions.getExtension(oid);
if (ext.getValue() != null) {
byte[] octs = ext.getValue().getOctets();
ASN1InputStream dIn = new ASN1InputStream(octs);
buf.append(" critical(").append(ext.isCritical()).append(") ");
try {
if (oid.equals(Extension.basicConstraints)) {
buf.append(BasicConstraints.getInstance((ASN1Sequence) dIn.readObject())).append(nl);
} else if (oid.equals(Extension.keyUsage)) {
buf.append(KeyUsage.getInstance((DERBitString) dIn.readObject())).append(nl);
} else if (oid.equals(MiscObjectIdentifiers.netscapeCertType)) {
buf.append(new NetscapeCertType((DERBitString) dIn.readObject())).append(nl);
} else if (oid.equals(MiscObjectIdentifiers.netscapeRevocationURL)) {
buf.append(new NetscapeRevocationURL((DERIA5String) dIn.readObject())).append(nl);
} else if (oid.equals(MiscObjectIdentifiers.verisignCzagExtension)) {
buf.append(new VerisignCzagExtension((DERIA5String) dIn.readObject())).append(nl);
//*********************************************************
// *** HERE: code to handle TNAuthorizationList ***
//*********************************************************
} else if (oid.equals(TNAuthorizationList.TN_AUTH_LIST_OID)) {
buf.append(TNAuthorizationList.getInstance((ASN1Sequence) dIn.readObject())).append(nl);
} else {
buf.append(oid.getId());
buf.append(" value = ").append(ASN1Dump.dumpAsString(dIn.readObject())).append(nl);
}
} catch (Exception ex) {
buf.append(oid.getId());
buf.append(" value = ").append("*****").append(nl);
}
} else {
buf.append(nl);
}
}
}
return buf.toString();
}
}
Then, you just use this wrapper instead of X509Certificate:
X509Certificate cert = ...
System.out.println("Certificate " + new CertificateWrapper(cert).format());
The output format is different from SUN's default provider, but you can customize it to get what you need.
PS: This question is a complement (or "sequel") to this one. The OP's code was provided here and TNAuthorizationList class is in the accepted answer. But as this question is a different issue (related, but different), it was kept as a separate question.
1.3.6.1.5.5.7.1.26 seems to be the OID for a Telephony Number (TN) Authorization List, which is only defined in a draft document (https://datatracker.ietf.org/doc/draft-ietf-stir-certificates/); BouncyCastle likely doesn't have a pretty printer for it, so it's showing you the raw encoded payload.
While I'm not an expert in telephony, I'm pretty sure that ('yuz8xxz', 'yuz8xxz') isn't a valid Service Provider Code list, and '123456'-'123456' is not a valid telephone number range. So it's pretty unclear what you're looking for with this certificate extension, and I'd be highly surprised if Let's Encrypt signed off on it.
Related
I have implemented Secure Web hook features for my Spring Boot application(Java).
For that I have created "Subscription" with below JSON.
String subscriptionMessageTemplate = "{\"changeType\": \"created,updated\",\"notificationUrl\": \"%s/notify/messages\",\"lifecycleNotificationUrl\":\"%s/notify/messages/lifeCycle\", \"resource\": \"/teams/{id}/channels/19:{id}#thread.skype/messages\", \"clientState\": \"secretClientValue\",\"includeResourceData\": true,\"encryptionCertificate\": \"%s\",\"expirationDateTime\":\"%s\",\"encryptionCertificateId\": \"1\"}";
I have used ngrok for public IP:
When I am sending message from the team, I am getting below response.
{
"value": [
{
"subscriptionId": "76222963-cc7b-42d2-882d-8aaa69cb2ba3",
"changeType": "created",
// Other properties typical in a resource change notification
"resource": "teams('d29828b8-c04d-4e2a-b2f6-07da6982f0f0')/channels('19:f127a8c55ad949d1a238464d22f0f99e#thread.skype')/messages('1565045424600')/replies('1565047490246')",
"resourceData": {
"id": "1565293727947",
"#odata.type": "#Microsoft.Graph.ChatMessage",
"#odata.id": "teams('88cbc8fc-164b-44f0-b6a6-b59b4a1559d3')/channels('19:8d9da062ec7647d4bb1976126e788b47#thread.tacv2')/messages('1565293727947')/replies('1565293727947')"
},
"encryptedContent": {
"data": "{encrypted data that produces a full resource}",
"dataSignature": "<HMAC-SHA256 hash>",
"dataKey": "{encrypted symmetric key from Microsoft Graph}",
"encryptionCertificateId": "MySelfSignedCert/DDC9651A-D7BC-4D74-86BC-A8923584B0AB",
"encryptionCertificateThumbprint": "07293748CC064953A3052FB978C735FB89E61C3D"
}
}
],
"validationTokens": [
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSU..."
]
}
Now I want to decrypt data, Can any one help me to how to decrypt data in Java?
For certificate generation, I have used my custom method:
strong text.
private void generateSelfSignedX509Certificate(KeyPair keyPair) throws Exception {
// yesterday
Date validityBeginDate = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
// in 2 years
Date validityEndDate = new Date(System.currentTimeMillis() + 2 * 365 * 24 * 60 * 60 * 1000);
// GENERATE THE X509 CERTIFICATE
X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
X500Principal dnName = new X500Principal("CN=John Doe");
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
certGen.setSubjectDN(dnName);
certGen.setIssuerDN(dnName); // use the same
certGen.setNotBefore(validityBeginDate);
certGen.setNotAfter(validityEndDate);
certGen.setPublicKey(keyPair.getPublic());
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
this.certificate = certGen.generate(keyPair.getPrivate(), "BC");
}
I had to struggle my way with it in Python 3.6, so for the sake of future python-readers, here is my skeleton of working code that do the above (using pycryptodome==3.9.7):
import json
import hashlib, hmac
from base64 import b64decode, b64encode
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Util.Padding import unpad
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES
...
...
encrypted_symmetric_key: bytes = b64decode(encrypted_symmetric_key.encode())
encrypted_payload = b64decode(encrypted_payload.encode())
rsa_key = RSA.import_key(private_key, passphrase=private_key_passphrase)
cipher = PKCS1_OAEP.new(rsa_key)
# if length of encrypted_symmetric_key is > 128 we will get ciphertext with incorrect length, to avoid that lets split and decrypt in chunks
default_length = 128
length = len(encrypted_symmetric_key)
if length < default_length:
decrypt_byte = cipher.decrypt(encrypted_symmetric_key)
else:
offset = 0
res = []
while length - offset > 0:
if length - offset > default_length:
res.append(cipher.decrypt(encrypted_symmetric_key[offset:offset + default_length]))
else:
res.append(cipher.decrypt(encrypted_symmetric_key[offset:]))
offset += default_length
decrypt_byte = b''.join(res)
decrypted_symmetric_key = decrypt_byte
hash_state_machine = hmac.new(decrypted_symmetric_key, msg=encrypted_payload, digestmod=hashlib.sha256)
raw_signature = hash_state_machine.digest()
actual_signature_bytes: bytes = b64encode(raw_signature)
actual_signature: str = actual_signature_bytes.decode()
if actual_signature != expected_data_signature:
raise Exception("data hash is not as expected")
iv = decrypted_symmetric_key[:16]
cipher2 = AES.new(decrypted_symmetric_key, AES.MODE_CBC, iv=iv)
message_str = unpad(cipher2.decrypt(encrypted_payload), block_size=16).decode()
message_dict = json.loads(message_str)
I updated the documentation to include Java samples. I'll also include the samples here for reference but future readers should refer to the documentation which is where the samples will be kept up to date.
Keep in mind these samples operate under the assumption that you have a local Java Key Store (JKS) that the certificate is pulled from.
Decrypt the AES key:
String storename = ""; //name/path of the jks store
String storepass = ""; //password used to open the jks store
String alias = ""; //alias of the certificate when store in the jks store, should be passed as encryptionCertificateId when subscribing and retrieved from the notification
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(storename), storepass.toCharArray());
Key asymmetricKey = ks.getKey(alias, storepass.toCharArray());
byte[] encryptedSymetricKey = Base64.decodeBase64("<value from dataKey property>");
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, asymmetricKey);
byte[] decryptedSymmetricKey = cipher.doFinal(encryptedSymetricKey);
Verify the data signature
byte[] decryptedSymmetricKey = "<the aes key decrypted in the previous step>";
byte[] decodedEncryptedData = Base64.decodeBase64("data property from encryptedContent object");
Mac mac = Mac.getInstance("HMACSHA256");
SecretKey skey = new SecretKeySpec(decryptedSymmetricKey, "HMACSHA256");
mac.init(skey);
byte[] hashedData = mac.doFinal(decodedEncryptedData);
String encodedHashedData = new String(Base64.encodeBase64(hashedData));
if (comparisonSignature.equals(encodedHashedData);)
{
// Continue with decryption of the encryptedPayload.
}
else
{
// Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
}
Decrypt the data content
SecretKey skey = new SecretKeySpec(decryptedSymmetricKey, "AES");
IvParameterSpec ivspec = new IvParameterSpec(Arrays.copyOf(decryptedSymmetricKey, 16));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skey, ivspec);
String decryptedResourceData = new String(cipher.doFinal(Base64.decodeBase64(encryptedData)));
optimized the previous answer a bit excluding the hash validations
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from config.config import Config
import base64
import json
config = Config('config.yaml')
notification_response = json.loads(msg)
base64encodedKey = notification_response['value'][0]['encryptedContent']['dataKey']
asymetricPrivateKey = bytes(config.subscription.PRIVATE_KEY,encoding='utf-8')
decodedKey = base64.b64decode(base64encodedKey)
private_key = serialization.load_pem_private_key(
asymetricPrivateKey,
password=None,
backend=default_backend()
)
decryptedSymetricKey = private_key.decrypt(
decodedKey,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
)
)
encrypted_payload = base64.b64decode(notification_response['value'][0]['encryptedContent']['data'].encode())
iv = decryptedSymetricKey[:16]
cipher2 = AES.new(decryptedSymetricKey, AES.MODE_CBC, iv=iv)
message_str = unpad(cipher2.decrypt(encrypted_payload), block_size=16).decode()
message_dict = json.loads(message_str)
I notice that Nginx selected 3 primes, 89, 113 and 6271, to implement the ip_hash load balancing method, why these 3 primes?
Is it more fair than just % number of servers?
$ factor 89 113 6271
89: 89
113: 113
6271: 6271
// https://github.com/nginx/nginx/blob/4bf4650f2f10f7bbacfe7a33da744f18951d416d/src/http/modules/ngx_http_upstream_ip_hash_module.c
static ngx_int_t
ngx_http_upstream_init_ip_hash_peer(ngx_http_request_t *r,
ngx_http_upstream_srv_conf_t *us)
{
// ...
ngx_http_upstream_ip_hash_peer_data_t *iphp;
iphp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_ip_hash_peer_data_t));
// ...
iphp->hash = 89;
// ...
}
static ngx_int_t
ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc, void *data)
{
// ...
for ( ;; ) {
for (i = 0; i < (ngx_uint_t) iphp->addrlen; i++) {
hash = (hash * 113 + iphp->addr[i]) % 6271;
}
// ...
}
// ...
iphp->hash = hash;
}
As the document said, "The first three octets of the client IPv4 address, or the entire IPv6 address, are used as a hashing key.", So let there be a IPv4 address, iphp->addrlen = 3, and iphp->addr[0] is the first octet of the IPv4 address, iphp->addr[1] is the second one, ...
I am using ESAPI Base64 encryption and decryption shown as is in:
http://www.programcreek.com/java-api-examples/index.php?api=org.owasp.esapi.codecs.Base64
This is how my code looks:
import org.owasp.esapi.crypto.CipherText;
import org.owasp.esapi.crypto.PlainText;
import org.owasp.esapi.errors.EncryptionException;
import org.owasp.esapi.reference.crypto.JavaEncryptor;
import javax.crypto.EncryptedPrivateKeyInfo
import org.owasp.esapi.ESAPI
import org.owasp.esapi.ValidationErrorList
import org.owasp.esapi.Validator
import org.apache.commons.codec.binary.Base64;
class SampleMain {
public String decrypt2(String cryptedText){
String clearText=null;
try {
CipherText cipherText=CipherText.fromPortableSerializedBytes(Base64.decodeBase64(cryptedText));
clearText=ESAPI.encryptor().decrypt(cipherText).toString();
}
catch ( EncryptionException e) {
System.out.println("EsapiEncryptor.decrypt: " + e.getMessage(),e);
}
return clearText.toString();
}
public String encrypt2(String clearText){
String cryptedText=null;
try {
CipherText cipherText=ESAPI.encryptor().encrypt(new PlainText(clearText));
cryptedText=Base64.encodeBase64(cipherText.asPortableSerializedByteArray());
}
catch ( EncryptionException e) {
System.out.println("EsapiEncryptor.encrypt: " + e.getMessage(),e);
}
return cryptedText;
}
public static void main(String[] args) throws EncryptionException{
String myplaintext = "MyPlaintext";
SampleMain sample = new SampleMain();
String enString = sample.encrypt2(myplaintext);
System.out.println("-----------enString-----------: " + enString);
String deString = sample.decrypt2(enString);
System.out.println("-----------deString-----------: " + deString);
}
}
But when I try to run this simple program i get the following exception:
Apr 01, 2017 12:43:30 PM org.owasp.esapi.reference.JavaLogFactory$JavaLogger log
WARNING: [SECURITY FAILURE Anonymous:null#unknown -> /DefaultName/IntrusionDetector] Likely tampering with KDF version on serialized ciphertext.KDF version read from serialized ciphertext (123190483) is out of range. Valid range for KDF version is [20110203, 99991231].
org.owasp.esapi.errors.EncryptionException: Version info from serialized ciphertext not in valid range.
at org.owasp.esapi.crypto.CipherTextSerializer.convertToCipherText(CipherTextSerializer.java:299)
at org.owasp.esapi.crypto.CipherTextSerializer.<init>(CipherTextSerializer.java:80)
at org.owasp.esapi.crypto.CipherText.fromPortableSerializedBytes(CipherText.java:176)
at org.owasp.esapi.crypto.CipherText$fromPortableSerializedBytes$0.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
at gov.gsa.dss.test.SampleMain.decrypt2(SampleMain.groovy:30)
at gov.gsa.dss.test.SampleMain$decrypt2$0.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
at gov.gsa.dss.test.SampleMain.main(SampleMain.groovy:59)
Any ideas why I would be getting this error or such a simple program. Thanks.
This works for me:
public String decrypt2(String encryptedText) {
byte[] encryptedTextTextAsBytes = encryptedText.getBytes(StandardCharsets.UTF_8)
CipherText cipherText = CipherText.fromPortableSerializedBytes(Base64.decodeBase64(encryptedTextTextAsBytes))
ESAPI.encryptor().decrypt(cipherText).toString()
}
public String encrypt2(String clearText) {
CipherText cipherText = ESAPI.encryptor().encrypt(new PlainText(clearText))
new String(Base64.encodeBase64(cipherText.asPortableSerializedByteArray()), StandardCharsets.UTF_8)
}
You are passing a String to Base64.decodeBase64(), it might compile but I'm not sure of what Groovy does with that. You should pass a bytes[] (see how I obtain encryptedTextTextAsBytes). It might explain your error, it might not. I guess you did not post the exact code that produces the error you mention.
I'm trying to make use of Java signatures in a pretty time sensitive setting.
I've come across some interesting behavior when signing data of a few hundred bytes. I am reusing a generated key, but recreating the Signature object each time I sign. The first time the signing happens it takes from anywhere from 50-100ms depending on the machine being used. However, any subsequent times a signature is computed over new data (using the same key) the time is reduced down to 1-2ms. I'm using SHA512 with RSA so I expected it to be heavier.
Can anyone explain why this happens? A test class I am using is pasted below (which is using identical code to my target applictaion).
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
public class MainClass {
public static void main(String args[]) throws Exception {
//Generate key pair
long start = System.currentTimeMillis();
KeyPair keyPair = generateKeyPair(999);
long end = System.currentTimeMillis();
System.out.println("KeyGen: " + (end - start));
//Sign first piece of data
byte[] data = { ** a few hundred bytes** };
start = System.currentTimeMillis();
byte[] digitalSignature = signData(data, keyPair.getPrivate());
end = System.currentTimeMillis();
System.out.println("Sign: " + (end - start));
boolean verified;
byte[] data2 = {** a different few hundred bytes ** };
//sign second piece of data
start = System.currentTimeMillis();
digitalSignature = signData(data2, keyPair.getPrivate());
end = System.currentTimeMillis();
System.out.println(" Second Sign: " + (end - start));
//verify second signature
start = System.currentTimeMillis();
verified = verifySig(data2, keyPair.getPublic(), digitalSignature);
end = System.currentTimeMillis();
System.out.println("Verify: " + (end - start));
System.out.println(verified);
}
public static byte[] signData(byte[] data, PrivateKey key) throws Exception {
Signature signer = Signature.getInstance("SHA512withRSA");
signer.initSign(key);
signer.update(data);
return (signer.sign());
}
public static boolean verifySig(byte[] data, PublicKey key, byte[] sig) throws Exception {
Signature signer = Signature.getInstance("SHA512withRSA");
signer.initVerify(key);
signer.update(data);
return (signer.verify(sig));
}
public static KeyPair generateKeyPair(long seed) throws Exception {
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom rng = SecureRandom.getInstance("SHA1PRNG", "SUN");
rng.setSeed(seed);
keyGenerator.initialize(1024, rng);
return (keyGenerator.generateKeyPair());
}
}
This results in the output (time in ms)
KeyGen: 81
Sign: 52
Second Sign: 2
Verify: 2
I put some benchmarks on each line of the sign method, and it appears the overhead is purely from the sign action itself.
Create Sig Obj: 1
Init sign: 1
update: 0
Sign: 49
Create Sig Obj: 1
Init sign: 0
update: 0
Second Sign: 2
Any insight would be much appreciated
Can someone point me to the preferred method for generating a report or document ID? I have been looking at maybe using a guid that would be reduced down to a shorter length. We have an application that creates an ID for reports that is about 8 characters long. They appear to be using some type of hash code. Probably using a base 36 encoding scheme. But I cant seem to find a way to make the hash code come out to a length of 8 characters since people have to use them to refer to the documents. They would also be used in a disconnected environment, so you couldnt look up the next usable serialized number in the chain. Just wondering what some of you use in applications like this?
The .net Framwork provides RNGCryptoServiceProvider class which Implements a cryptographic Random Number Generator (RNG) using the implementation provided by the cryptographic service provider (CSP). This class is usually used to generate random numbers. Although I can use this class to generate unique number in some sense but it is also not collision less. Moreover while generating key we can make key more complicated by making it as alpha numeric rather than numeric only. So, I used this class along with some character masking to generate unique key of fixed length.
private string GetUniqueKey()
{
int maxSize = 8 ;
int minSize = 5 ;
char[] chars = new char[62];
string a;
a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
chars = a.ToCharArray();
int size = maxSize ;
byte[] data = new byte[1];
RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
crypto.GetNonZeroBytes(data) ;
size = maxSize ;
data = new byte[size];
crypto.GetNonZeroBytes(data);
StringBuilder result = new StringBuilder(size) ;
foreach(byte b in data )
{ result.Append(chars[__b % (chars.Length - )>); }
<span class="code-keyword">return result.ToString();
}
http://www.codeproject.com/Articles/14403/Generating-Unique-Keys-in-Net
This is what I ended up using. It is a base36 encoding. I borrowed parts of the code from other people, so I cant claim that I wrote it all, but I hope this helps others. This will produce about a 12 digit record ID, or unique ID for databases etc. It uses only the last 2 digits of the year, so it should be good for 100 years.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Base36Converter
{
public partial class Form1 : Form
{
private const string CharList = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public Form1()
{
InitializeComponent();
}
//Base 36 number consists of only numbers and uppercase letters only.
private void button1_Click(object sender, EventArgs e)
{
if (textBox2.Text.Length > 0)
{
label3.Text = "";
//Get Date and Time Stamp
string temp1 = GetTimestamp(DateTime.Now);
//Turn it into a long number
long l = Convert.ToInt64(temp1);
//Now encode it as a base36 number.
string s1 = Encode(l);
//Get userID as a number, i.e. 1055 (User's index number) and create as a long type.
long l1 = Convert.ToInt64(textBox2.Text);
//Encode it as a base36 number.
string s2 = Encode(l1);
//Now display it as the encoded user number + datetime encoded number (Concatenated)
textBox1.Text = s2 + s1;
}
else
{
label3.Text = "User Number must be greater than 0. ie 1055";
}
}
public static String Encode(long input)
{
if (input < 0) throw new ArgumentOutOfRangeException("input", input, "input cannot be negative");
char[] clistarr = CharList.ToCharArray();
var result = new Stack<char>();
while (input != 0)
{
result.Push(clistarr[input % 36]);
input /= 36;
}
return new string(result.ToArray());
}
public static String GetTimestamp(DateTime value)
{
return value.ToString("yyMMddHHmmssffff");
}
private void Form1_Load(object sender, EventArgs e)
{
label3.Text = "";
}
}
}