Java 256 bit AES Parola Tabanlı Şifreleme


390

256 bit AES şifrelemesi uygulamak gerekiyor, ancak çevrimiçi bulduk tüm örnekler 256 bit anahtar oluşturmak için bir "KeyGenerator" kullanın, ancak kendi parolamı kullanmak istiyorum. Kendi anahtarımı nasıl oluşturabilirim? 256 bit kadar doldurmayı denedim, ama sonra anahtarın çok uzun olduğunu söyleyen bir hata alıyorum. Ben sınırsız yargılama yama yüklü var, bu yüzden sorun değil :)

Yani. KeyGenerator şöyle görünüyor ...

// Get the KeyGenerator
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available

// Generate the secret key specs.
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();

Kod buradan alınır

DÜZENLE

Aslında şifreyi çok uzun olan bitlere değil, 256 bayta kadar dolduruyordum. Aşağıdaki ben bu konuda biraz daha deneyimim var şimdi kullanıyorum bazı kod.

byte[] key = null; // TODO
byte[] input = null; // TODO
byte[] output = null;
SecretKeySpec keySpec = null;
keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
output = cipher.doFinal(input)

Kendiniz yapmanız gereken "YAPILACAKLAR" bitleri :-)


Açıklığa kavuşturabilir misiniz: kgen.init (256) çağrısı işe yarıyor mu?
Mitch Wheat

2
Evet, ama bu otomatik olarak bir anahtar oluşturur ... ama iki yer arasında veri şifrelemek istiyorum, önceden anahtarı bilmek gerekir, bu yüzden "oluşturmak" bir yerine belirtmek gerekir. Çalışır 128bit şifreleme için çalışan bir 16bit bir belirtebilirsiniz. 256bit şifreleme için 32bit bir tane denedim, ancak beklendiği gibi çalışmadı.
Nippysaurus

4
Doğru anlıyorsam, örneğin bir bayt dizisi olarak belirtilen önceden düzenlenmiş 256 bit bir anahtar kullanmaya çalışıyorsunuz. Öyleyse, DarkSquid'in SecretKeySpec'i kullanma yaklaşımı işe yarayacaktır. Bir paroladan bir AES anahtarı türetmek de mümkündür; eğer peşinde olduğun buysa, lütfen bana bildir, ben de bunu yapmanın doğru yolunu göstereceğim; sadece bir parola oluşturmak en iyi yöntem değildir.
erickson

Bir numarayı doldurmaya dikkat edin, AES'nizi daha az güvenli hale getiriyor olabilirsiniz.
Joshua

1
@erickson: tam olarak ne yapmam gerekiyor (bir şifre bir AES anahtarı türetmek).
Nippysaurus

Yanıtlar:


475

password(A char[]) ve salt(a byte[]—8 bayt tarafından bir SecureRandommarka tarafından seçilen ve gizli tutulması gerekmeyen iyi bir tuz yapar) bant dışı alıcıyla paylaşın . Sonra bu bilgilerden iyi bir anahtar elde etmek için:

/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

Sihirli sayılar (bir yerlerde sabitler olarak tanımlanabilir) 65536 ve 256 sırasıyla anahtar türev yineleme sayısı ve anahtar boyutudur.

Anahtar türetme işlevi, önemli hesaplama çabaları gerektirecek şekilde yinelenir ve bu, saldırganların birçok farklı şifreyi hızlı bir şekilde denemelerini önler. Yineleme sayısı, mevcut bilgi işlem kaynaklarına bağlı olarak değiştirilebilir.

Anahtar boyutu 128 bite kadar azaltılabilir, bu da yine de "güçlü" şifreleme olarak kabul edilir, ancak AES'yi zayıflatan saldırılar tespit edilirse, bir güvenlik payı vermez.

Uygun bir blok zincirleme modu ile kullanıldığında, aynı türetilmiş anahtar birçok mesajı şifrelemek için kullanılabilir. Olarak şifre bloğu zinciri (CBC) , rastgele bir başlatma vektörü (IV) 'düz metin aynı olsa bile, farklı bir şifre metin, sonuçta her mesaj için oluşturulur. CBC kullanabileceğiniz en güvenli mod olmayabilir (aşağıdaki AEAD'a bakın); farklı güvenlik özelliklerine sahip başka birçok mod vardır, ancak hepsi benzer rastgele bir giriş kullanır. Her durumda, her şifreleme işleminin çıktıları şifre metni ve başlatma vektörüdür:

/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal("Hello, World!".getBytes("UTF-8"));

Mağaza ciphertextve iv. Şifre çözme işleminde, SecretKeyaynı tuz ve yineleme parametreleriyle parola kullanılarak tam olarak aynı şekilde yeniden oluşturulur. Şifreyi bu tuşla ve mesajla birlikte verilen başlatma vektörünü başlatın :

/* Decrypt the message, given derived key and initialization vector. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8");
System.out.println(plaintext);

Java 7 , AEAD şifreleme modları için API desteği içeriyor ve OpenJDK ve Oracle dağıtımlarına dahil olan "SunJCE" sağlayıcısı, Java 8 ile başlayarak bunları uyguluyor. CBC yerine bu modlardan biri şiddetle önerilir; verilerin bütünlüğünü ve gizliliğini koruyacaktır.


java.security.InvalidKeyException"Geçersiz anahtar boyutu veya varsayılan parametreler" mesajıyla A , şifreleme gücünün sınırlı olduğu anlamına gelir ; sınırsız güç yetkisi ilke dosyaları doğru konumda değil. Bir JDK'da,${jdk}/jre/lib/security

Sorun açıklamasına bağlı olarak, ilke dosyaları doğru yüklenmemiş gibi görünüyor. Sistemlerin birden fazla Java çalışma zamanı olabilir; doğru konumun kullanıldığından emin olmak için iki kez kontrol edin.


29
@Nick: PKCS # 5'i okuyun. PBKDF2 için tuzlar gereklidir, bu yüzden parola tabanlı şifreleme için API, anahtar türetme için girdi olarak bunları gerektirir. Tuzlar olmadan, büyük olasılıkla simetrik şifreleme anahtarlarının önceden hesaplanmış bir listesini sağlayan bir sözlük saldırısı kullanılabilir. Şifre IV'ler ve anahtar türev tuzları farklı amaçlara hizmet eder. IV'ler aynı anahtarın birden fazla mesaj için yeniden kullanılmasına izin verir. Tuzlar anahtar üzerinde sözlük saldırılarını önler.
erickson

2
Birincisi, bu AES değil DES şifreleme olurdu. Çoğu sağlayıcı PBEwith<prf>and<encryption>algoritmalar için iyi bir desteğe sahip değildir ; örneğin, SunJCE AES için PBE sağlamamaktadır. İkincisi, jasypt'i etkinleştirmek hedef değildir. Temel prensiplerin anlaşılmasını gerektirmeden güvenlik sunmayı amaçlayan bir paket tehlikeli prima facie gibi görünüyor.
erickson

6
@ Erickson'un cevabını bir sınıf olarak uyguladım: github.com/mrclay/jSecureEdit/tree/master/src/org/mrclay/crypto (PBE işi ​​yapar, PBEStorage IV / şifreleme metnini birlikte depolamak için bir değer nesnesidir.)
Steve Clay

3
@AndyNuss Bu örnek, genellikle parolalar için kullanılmaması gereken tersine çevrilebilir şifreleme içindir. Sen edebilirsiniz güvenli "hash" şifrelerine PBKDF2 anahtar türetimi kullanın. Bu, yukarıdaki örnekte, sonucu tmp.getEncoded()karma olarak saklayacağınız anlamına gelir . saltBirisi kimlik doğrulaması yapmaya çalıştığında karma değerini yeniden hesaplayabilmeniz için ve yinelemeleri de (bu örnekte 65536) saklamanız gerekir . Bu durumda, şifreyi her değiştirdiğinizde tuz bir kriptografik rasgele sayı üreteci ile üretilir.
erickson

6
Bu kodu çalıştırmak için, JRE'nizde ngs.ac.uk/tools/jcepolicyfiles
Amir Moghimi

75

Spring Security Kripto Modülünü kullanmayı düşünün

Spring Security Crypto modülü, simetrik şifreleme, anahtar üretimi ve şifre kodlaması için destek sağlar. Kod, çekirdek modülün bir parçası olarak dağıtılır, ancak diğer Spring Security (veya Spring) kodlarına bağımlılığı yoktur.

Şifreleme için basit bir soyutlama sağlar ve burada gerekli olanla eşleşir,

"Standart" şifreleme yöntemi, PKCS # 5'in PBKDF2'sini (Parola Tabanlı Anahtar Türetme Fonksiyonu # 2) kullanan 256 bit AES'dir. Bu yöntem Java 6 gerektirir. SecretKey'i oluşturmak için kullanılan parola güvenli bir yerde saklanmalı ve paylaşılmamalıdır. Tuz, şifrelenmiş verilerinizin ele geçirilmesi durumunda anahtara karşı sözlük saldırılarını önlemek için kullanılır. 16 baytlık rasgele başlatma vektörü de uygulanır, böylece her şifreli mesaj benzersizdir.

Bir bakış iç yapısına benzer bir yapı ortaya koymaktadır Erickson'ın cevap .

Soruda belirtildiği gibi, bu ayrıca Java Şifreleme Uzantısı (JCE) Sınırsız Güç Yetkilendirme Politikası'nı (karşılaşacağınız başka bir şey InvalidKeyException: Illegal Key Size) gerektirir. Java 6 , Java 7 ve Java 8 için indirilebilir .

Örnek kullanım

import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.crypto.keygen.KeyGenerators;

public class CryptoExample {
    public static void main(String[] args) {
        final String password = "I AM SHERLOCKED";  
        final String salt = KeyGenerators.string().generateKey();

        TextEncryptor encryptor = Encryptors.text(password, salt);      
        System.out.println("Salt: \"" + salt + "\"");

        String textToEncrypt = "*royal secrets*";
        System.out.println("Original text: \"" + textToEncrypt + "\"");

        String encryptedText = encryptor.encrypt(textToEncrypt);
        System.out.println("Encrypted text: \"" + encryptedText + "\"");

        // Could reuse encryptor but wanted to show reconstructing TextEncryptor
        TextEncryptor decryptor = Encryptors.text(password, salt);
        String decryptedText = decryptor.decrypt(encryptedText);
        System.out.println("Decrypted text: \"" + decryptedText + "\"");

        if(textToEncrypt.equals(decryptedText)) {
            System.out.println("Success: decrypted text matches");
        } else {
            System.out.println("Failed: decrypted text does not match");
        }       
    }
}

Ve örnek çıktı,

Tuz: "feacbc02a3a697b0"
Asıl metin: "* kraliyet sırları *"
Şifreli metin: "7c73c5a83fa580b5d6f8208768adc931ef3123291ac8bc335a1277a39d256d9a" 
Şifresi çözülmüş metin: "* kraliyet sırları *"
Başarı: şifresi çözülmüş metin eşleşmeleri

Bu modülü tüm Spring'i yüklemeden kullanabilir misiniz? Jar dosyalarını indirmeye hazır hale getirmemiş gibi görünüyorlar.
theglauber

5
@theglauber Evet, modülü Spring Security veya Spring framework olmadan kullanabilirsiniz. Pom'a bakıldığında , tek çalışma zamanı bağımlılığı apache commons-logging 1.1.1'dir . Sen edebilirsiniz maven ile kavanozda çekin veya resmi ikili repo doğrudan indirir (bkz Bahar 4 ikili indir Bahar binarilerde daha fazla bilgi için).
John McCarthy

1
Anahtar uzunluğunu 128 bit olarak ayarlamak mümkün müdür? Her bilgisayarda güvenlik klasörünü değiştirmek benim için bir seçenek değil.
IvanRF

1
@IvanRF üzgünüm, öyle görünmüyor. 256 kaynak
John McCarthy

2
NULL_IV_GENERATORYay aracı tarafından kullanılan güvenli değildir. Uygulama bir IV sağlamazsa, sağlayıcının seçmesine izin verin ve başlattıktan sonra sorgulayın.
erickson

32

Erickson'ın önerileri okuduktan ve ben birkaç diğer ilanlara ve bu örnekten ne olabilir gleaning sonra burada , ben tavsiye değişikliklerle Doug'un kodunu güncellemek çalıştınız. Daha iyi hale getirmek için düzenlemekten çekinmeyin.

  • Başlatma Vektörü artık sabit değil
  • şifreleme anahtarı erickson kodu kullanılarak türetilmiştir
  • SecureRandom () kullanılarak setupEncrypt () içinde 8 bayt tuzu üretilir
  • şifre çözme anahtarı şifreleme tuzu ve şifresinden üretilir
  • şifre çözme şifre çözme anahtar ve başlatma vektöründen oluşturulur
  • org.apache.commons yerine kaldırılan heks Raketi çevirmek kodek Hex rutinleri

Bazı notlar: Bu, 128 bit şifreleme anahtarı kullanır - java görünüşte kutudan çıktığı gibi 256 bit şifreleme yapmayacaktır. 256'nın uygulanması, java kurulum dizinine fazladan bazı dosyaların yüklenmesini gerektirir.

Ayrıca, ben bir kripto insanı değilim. Dikkat etmek.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

public class Crypto
{
    String mPassword = null;
    public final static int SALT_LEN = 8;
    byte [] mInitVec = null;
    byte [] mSalt = null;
    Cipher mEcipher = null;
    Cipher mDecipher = null;
    private final int KEYLEN_BITS = 128; // see notes below where this is used.
    private final int ITERATIONS = 65536;
    private final int MAX_FILE_BUF = 1024;

    /**
     * create an object with just the passphrase from the user. Don't do anything else yet 
     * @param password
     */
    public Crypto (String password)
    {
        mPassword = password;
    }

    /**
     * return the generated salt for this object
     * @return
     */
    public byte [] getSalt ()
    {
        return (mSalt);
    }

    /**
     * return the initialization vector created from setupEncryption
     * @return
     */
    public byte [] getInitVec ()
    {
        return (mInitVec);
    }

    /**
     * debug/print messages
     * @param msg
     */
    private void Db (String msg)
    {
        System.out.println ("** Crypt ** " + msg);
    }

    /**
     * this must be called after creating the initial Crypto object. It creates a salt of SALT_LEN bytes
     * and generates the salt bytes using secureRandom().  The encryption secret key is created 
     * along with the initialization vectory. The member variable mEcipher is created to be used
     * by the class later on when either creating a CipherOutputStream, or encrypting a buffer
     * to be written to disk.
     *  
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidParameterSpecException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     * @throws InvalidKeyException
     */
    public void setupEncrypt () throws NoSuchAlgorithmException, 
                                                           InvalidKeySpecException, 
                                                           NoSuchPaddingException, 
                                                           InvalidParameterSpecException, 
                                                           IllegalBlockSizeException, 
                                                           BadPaddingException, 
                                                           UnsupportedEncodingException, 
                                                           InvalidKeyException
    {
        SecretKeyFactory factory = null;
        SecretKey tmp = null;

        // crate secureRandom salt and store  as member var for later use
         mSalt = new byte [SALT_LEN];
        SecureRandom rnd = new SecureRandom ();
        rnd.nextBytes (mSalt);
        Db ("generated salt :" + Hex.encodeHexString (mSalt));

        factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

        /* Derive the key, given password and salt. 
         * 
         * in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
         * The end user must also install them (not compiled in) so beware. 
         * see here:  http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
         */
        KeySpec spec = new PBEKeySpec (mPassword.toCharArray (), mSalt, ITERATIONS, KEYLEN_BITS);
        tmp = factory.generateSecret (spec);
        SecretKey secret = new SecretKeySpec (tmp.getEncoded(), "AES");

        /* Create the Encryption cipher object and store as a member variable
         */
        mEcipher = Cipher.getInstance ("AES/CBC/PKCS5Padding");
        mEcipher.init (Cipher.ENCRYPT_MODE, secret);
        AlgorithmParameters params = mEcipher.getParameters ();

        // get the initialization vectory and store as member var 
        mInitVec = params.getParameterSpec (IvParameterSpec.class).getIV();

        Db ("mInitVec is :" + Hex.encodeHexString (mInitVec));
    }



    /**
     * If a file is being decrypted, we need to know the pasword, the salt and the initialization vector (iv). 
     * We have the password from initializing the class. pass the iv and salt here which is
     * obtained when encrypting the file initially.
     *   
     * @param initvec
     * @param salt
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws DecoderException
     */
    public void setupDecrypt (String initvec, String salt) throws NoSuchAlgorithmException, 
                                                                                       InvalidKeySpecException, 
                                                                                       NoSuchPaddingException, 
                                                                                       InvalidKeyException, 
                                                                                       InvalidAlgorithmParameterException, 
                                                                                       DecoderException
    {
        SecretKeyFactory factory = null;
        SecretKey tmp = null;
        SecretKey secret = null;

        // since we pass it as a string of input, convert to a actual byte buffer here
        mSalt = Hex.decodeHex (salt.toCharArray ());
       Db ("got salt " + Hex.encodeHexString (mSalt));

        // get initialization vector from passed string
        mInitVec = Hex.decodeHex (initvec.toCharArray ());
        Db ("got initvector :" + Hex.encodeHexString (mInitVec));


        /* Derive the key, given password and salt. */
        // in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
        // The end user must also install them (not compiled in) so beware. 
        // see here: 
      // http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
        factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(mPassword.toCharArray (), mSalt, ITERATIONS, KEYLEN_BITS);

        tmp = factory.generateSecret(spec);
        secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        /* Decrypt the message, given derived key and initialization vector. */
        mDecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        mDecipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(mInitVec));
    }


    /**
     * This is where we write out the actual encrypted data to disk using the Cipher created in setupEncrypt().
     * Pass two file objects representing the actual input (cleartext) and output file to be encrypted.
     * 
     * there may be a way to write a cleartext header to the encrypted file containing the salt, but I ran
     * into uncertain problems with that. 
     *  
     * @param input - the cleartext file to be encrypted
     * @param output - the encrypted data file
     * @throws IOException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public void WriteEncryptedFile (File input, File output) throws 
                                                                                          IOException, 
                                                                                          IllegalBlockSizeException, 
                                                                                          BadPaddingException
    {
        FileInputStream fin;
        FileOutputStream fout;
        long totalread = 0;
        int nread = 0;
        byte [] inbuf = new byte [MAX_FILE_BUF];

        fout = new FileOutputStream (output);
        fin = new FileInputStream (input);

        while ((nread = fin.read (inbuf)) > 0 )
        {
            Db ("read " + nread + " bytes");
            totalread += nread;

            // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
            // and results in full blocks of MAX_FILE_BUF being written. 
            byte [] trimbuf = new byte [nread];
            for (int i = 0; i < nread; i++)
                trimbuf[i] = inbuf[i];

            // encrypt the buffer using the cipher obtained previosly
            byte [] tmp = mEcipher.update (trimbuf);

            // I don't think this should happen, but just in case..
            if (tmp != null)
                fout.write (tmp);
        }

        // finalize the encryption since we've done it in blocks of MAX_FILE_BUF
        byte [] finalbuf = mEcipher.doFinal ();
        if (finalbuf != null)
            fout.write (finalbuf);

        fout.flush();
        fin.close();
        fout.close();

        Db ("wrote " + totalread + " encrypted bytes");
    }


    /**
     * Read from the encrypted file (input) and turn the cipher back into cleartext. Write the cleartext buffer back out
     * to disk as (output) File.
     * 
     * I left CipherInputStream in here as a test to see if I could mix it with the update() and final() methods of encrypting
     *  and still have a correctly decrypted file in the end. Seems to work so left it in.
     *  
     * @param input - File object representing encrypted data on disk 
     * @param output - File object of cleartext data to write out after decrypting
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public void ReadEncryptedFile (File input, File output) throws 
                                                                                                                                            IllegalBlockSizeException, 
                                                                                                                                            BadPaddingException, 
                                                                                                                                            IOException
    {
        FileInputStream fin; 
        FileOutputStream fout;
        CipherInputStream cin;
        long totalread = 0;
        int nread = 0;
        byte [] inbuf = new byte [MAX_FILE_BUF];

        fout = new FileOutputStream (output);
        fin = new FileInputStream (input);

        // creating a decoding stream from the FileInputStream above using the cipher created from setupDecrypt()
        cin = new CipherInputStream (fin, mDecipher);

        while ((nread = cin.read (inbuf)) > 0 )
        {
            Db ("read " + nread + " bytes");
            totalread += nread;

            // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
            byte [] trimbuf = new byte [nread];
            for (int i = 0; i < nread; i++)
                trimbuf[i] = inbuf[i];

            // write out the size-adjusted buffer
            fout.write (trimbuf);
        }

        fout.flush();
        cin.close();
        fin.close ();       
        fout.close();   

        Db ("wrote " + totalread + " encrypted bytes");
    }


    /**
     * adding main() for usage demonstration. With member vars, some of the locals would not be needed
     */
    public static void main(String [] args)
    {

        // create the input.txt file in the current directory before continuing
        File input = new File ("input.txt");
        File eoutput = new File ("encrypted.aes");
        File doutput = new File ("decrypted.txt");
        String iv = null;
        String salt = null;
        Crypto en = new Crypto ("mypassword");

        /*
         * setup encryption cipher using password. print out iv and salt
         */
        try
      {
          en.setupEncrypt ();
          iv = Hex.encodeHexString (en.getInitVec ()).toUpperCase ();
          salt = Hex.encodeHexString (en.getSalt ()).toUpperCase ();
      }
      catch (InvalidKeyException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchAlgorithmException e)
      {
          e.printStackTrace();
      }
      catch (InvalidKeySpecException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchPaddingException e)
      {
          e.printStackTrace();
      }
      catch (InvalidParameterSpecException e)
      {
          e.printStackTrace();
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (UnsupportedEncodingException e)
      {
          e.printStackTrace();
      }

        /*
         * write out encrypted file
         */
        try
      {
          en.WriteEncryptedFile (input, eoutput);
          System.out.printf ("File encrypted to " + eoutput.getName () + "\niv:" + iv + "\nsalt:" + salt + "\n\n");
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (IOException e)
      {
          e.printStackTrace();
      }


        /*
         * decrypt file
         */
        Crypto dc = new Crypto ("mypassword");
        try
      {
          dc.setupDecrypt (iv, salt);
      }
      catch (InvalidKeyException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchAlgorithmException e)
      {
          e.printStackTrace();
      }
      catch (InvalidKeySpecException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchPaddingException e)
      {
          e.printStackTrace();
      }
      catch (InvalidAlgorithmParameterException e)
      {
          e.printStackTrace();
      }
      catch (DecoderException e)
      {
          e.printStackTrace();
      }

        /*
         * write out decrypted file
         */
        try
      {
          dc.ReadEncryptedFile (eoutput, doutput);
          System.out.println ("decryption finished to " + doutput.getName ());
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (IOException e)
      {
          e.printStackTrace();
      }
   }


}

13
Bu temelde Erickson'un cevabı ile aynı cevaptır - benim görüşüme göre o kadar iyi programlanmamış - sarıcı ile çevrilidir. printStackTrace()
Maarten Bodewes

2
@owlstead - Bu harika bir cevap. Bellekteki her şeye sahip olmak yerine bayt arabelleğini şifreleyerek bir akışın nasıl şifreleneceğini gösterir. Erickson'un cevabı belleğe sığmayan büyük dosyalar için çalışmaz. Yani wufoo'ya +1. :)
dynamokaj 16:14

2
@dynamokaj Kullanımı CipherInputStreamve CipherOutputStreambir sorun değil. Masanın altındaki tüm istisnaları karıştırmak bir sorundur. Tuzun aniden bir alan haline gelmesi ve IV'ün gerekli olması bir sorundur. Java kodlama kurallarına uymaması bir sorundur. Ve bunun yalnızca istenmediğinde dosyalar üzerinde çalışması bir sorundur. Ve kodun geri kalanının temelde bir kopyası olması da yardımcı olmaz. Ama belki de önerdiğim gibi daha iyi yapmak için
ayarlayacağım

@owlstead Kodlamanın daha iyi göründüğüne katılıyorum, onu 1/4 ya da başka bir şeye indirdim, ama beni dün CipherInputStream ve CipherOutputStream ile tanıştırdı, çünkü dün ihtiyacım olan buydu! ;)
dynamokaj

neden iki kez? ) (fout.close; fout.close ();
Marian Paździoch

7

Bayt dizisinden kendi anahtarınızı oluşturmak kolaydır:

byte[] raw = ...; // 32 bytes in size for a 256 bit key
Key skey = new javax.crypto.spec.SecretKeySpec(raw, "AES");

Ancak 256 bit anahtar oluşturmak yeterli değildir. Anahtar oluşturucu sizin için 256 bit anahtar üretemezse, Ciphersınıf muhtemelen AES 256 bit'i de desteklemez. Sınırsız yetki alanı düzeltme ekinin yüklü olduğunu söylüyorsunuz, bu nedenle AES-256 şifrelemesi desteklenmelidir (ancak 256 bit anahtarlar da olmalıdır, bu yüzden bu bir yapılandırma sorunu olabilir).

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
byte[] encrypted = cipher.doFinal(plainText.getBytes());

AES-256 desteğinin olmaması için geçici bir çözüm, AES-256'nın ücretsiz olarak kullanılabilen bazı uygulamalarını alıp özel bir sağlayıcı olarak kullanmaktır. Bu, kendi Provideralt sınıfınızı oluşturmayı ve birlikte kullanmayı içerir Cipher.getInstance(String, Provider). Ancak bu, ilgili bir süreç olabilir.


5
Her zaman modu ve dolgu algoritmasını belirtmelisiniz. Java varsayılan olarak güvenli olmayan ECB modunu kullanır.
Maarten Bodewes

Kendi sağlayıcınızı oluşturamazsınız, sağlayıcılar imzalanmalıdır (başlangıçta bu hatayı okuduğuma inanamıyorum). Yapabilseniz bile, anahtar boyutunun kısıtlanması Cipher, sağlayıcının kendisinde değil , uygulamasındadır . Java 8 ve daha düşük sürümlerde AES-256 kullanabilirsiniz, ancak tescilli bir API kullanmanız gerekir. Ya da anahtar boyutunda kısıtlama getirmeyen bir çalışma zamanı.
Maarten Bodewes

OpenJDK'nın (ve Android'in) son sürümlerinin kendi güvenlik / kripto sağlayıcınızı eklemeyle ilgili kısıtlamaları yoktur. Ama bunu elbette kendi sorumluluğunuzda yapabilirsiniz. Kütüphanelerinizi güncel tutmayı unutursanız, kendinizi güvenlik risklerine maruz bırakabilirsiniz.
Maarten Bodewes

1
@ MaartenBodewes + OpenJDK hiçbir zaman 'sınırlı kripto politikası' sorununu hiç yaşamadı ve Oracle JDK bir yıl önce 8u161 ve 9 için kaldırdı (ve belki de daha düşük ücretli bazı sürümleri kontrol etmedim ama bunları kontrol etmedim)
dave_thompson_085

6

Geçmişte yaptığım şey SHA256 gibi bir şeyle hash anahtarıdır, daha sonra hashtan gelen baytları anahtar baytına [] çıkarın.

Baytınızı [] aldıktan sonra şunları yapabilirsiniz:

SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(clearText.getBytes());

12
Diğerleri için: bu çok güvenli bir yöntem değildir. PKCS # 5'te belirtilen PBKDF 2'yi kullanmalısınız. erickson bunu nasıl yapacağını söyledi. DarkSquid'in yöntemi şifre saldırılarına karşı savunmasızdır ve düz metninizin boyutu AES'in blok boyutunun (128 bit) katı olmadığı için çalışmaz. Ayrıca modu belirtmez; endişeler için Wikipedia'nın Blok Şifreleme Çalışma Modlarını okuyun.
Hut8

1
@DarkSquid Cipher aes256 = Cipher.getInstance("AES/OFB/NoPadding"); MessageDigest keyDigest = MessageDigest.getInstance("SHA-256"); byte[] keyHash = keyDigest.digest(secret.getBytes("UTF-8")); SecretKeySpec key = new SecretKeySpec(keyHash, "AES"); aes256.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(initializationVector)); Ben de cevabınız önerilen ile aynı yapıyorum ama hala bu java.security.InvalidKeyException: Geçersiz anahtar boyutu JCE ilke dosyası indirmek zorunlu mu?
Niranjan Subramanian

2
Bu yöntemi herhangi bir üretim ortamında KULLANMAYIN. Parola Tabanlı şifreleme ile başlarken, birçok kullanıcı kod duvarları tarafından boğulmuş ve sözlük saldırılarının ve diğer basit saldırıların nasıl çalıştığını anlamıyor. Öğrenmek sinir bozucu olsa da, bunu araştırmak değerli bir yatırımdır. İşte iyi bir başlangıç ​​makalesi: adambard.com/blog/3-wrong-ways-to-store-a-password
IcedDante

1

@ Wufoo'nun düzenlemelerine ek olarak, aşağıdaki sürüm çeşitli dosyalarla çalışmayı kolaylaştırmak için dosyalar yerine InputStreams kullanır. Ayrıca IV ve Salt'ı dosyanın başlangıcında saklar, böylece sadece parolanın izlenmesi gerekir. IV ve Tuzun gizli olması gerekmediğinden, hayatı biraz kolaylaştırır.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
    public final static int SALT_LEN     = 8;
    static final String     HEXES        = "0123456789ABCDEF";
    String                  mPassword    = null;
    byte[]                  mInitVec     = null;
    byte[]                  mSalt        = new byte[SALT_LEN];
    Cipher                  mEcipher     = null;
    Cipher                  mDecipher    = null;
    private final int       KEYLEN_BITS  = 128;    // see notes below where this is used.
    private final int       ITERATIONS   = 65536;
    private final int       MAX_FILE_BUF = 1024;

    /**
     * create an object with just the passphrase from the user. Don't do anything else yet
     * @param password
     */
    public AES(String password) {
        mPassword = password;
    }

    public static String byteToHex(byte[] raw) {
        if (raw == null) {
            return null;
        }

        final StringBuilder hex = new StringBuilder(2 * raw.length);

        for (final byte b : raw) {
            hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
        }

        return hex.toString();
    }

    public static byte[] hexToByte(String hexString) {
        int    len = hexString.length();
        byte[] ba  = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            ba[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                                + Character.digit(hexString.charAt(i + 1), 16));
        }

        return ba;
    }

    /**
     * debug/print messages
     * @param msg
     */
    private void Db(String msg) {
        System.out.println("** Crypt ** " + msg);
    }

    /**
     * This is where we write out the actual encrypted data to disk using the Cipher created in setupEncrypt().
     * Pass two file objects representing the actual input (cleartext) and output file to be encrypted.
     *
     * there may be a way to write a cleartext header to the encrypted file containing the salt, but I ran
     * into uncertain problems with that.
     *
     * @param input - the cleartext file to be encrypted
     * @param output - the encrypted data file
     * @throws IOException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public void WriteEncryptedFile(InputStream inputStream, OutputStream outputStream)
            throws IOException, IllegalBlockSizeException, BadPaddingException {
        try {
            long             totalread = 0;
            int              nread     = 0;
            byte[]           inbuf     = new byte[MAX_FILE_BUF];
            SecretKeyFactory factory   = null;
            SecretKey        tmp       = null;

            // crate secureRandom salt and store  as member var for later use
            mSalt = new byte[SALT_LEN];

            SecureRandom rnd = new SecureRandom();

            rnd.nextBytes(mSalt);
            Db("generated salt :" + byteToHex(mSalt));
            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

            /*
             *  Derive the key, given password and salt.
             *
             * in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
             * The end user must also install them (not compiled in) so beware.
             * see here:  http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
             */
            KeySpec spec = new PBEKeySpec(mPassword.toCharArray(), mSalt, ITERATIONS, KEYLEN_BITS);

            tmp = factory.generateSecret(spec);

            SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

            /*
             *  Create the Encryption cipher object and store as a member variable
             */
            mEcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            mEcipher.init(Cipher.ENCRYPT_MODE, secret);

            AlgorithmParameters params = mEcipher.getParameters();

            // get the initialization vectory and store as member var
            mInitVec = params.getParameterSpec(IvParameterSpec.class).getIV();
            Db("mInitVec is :" + byteToHex(mInitVec));
            outputStream.write(mSalt);
            outputStream.write(mInitVec);

            while ((nread = inputStream.read(inbuf)) > 0) {
                Db("read " + nread + " bytes");
                totalread += nread;

                // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
                // and results in full blocks of MAX_FILE_BUF being written.
                byte[] trimbuf = new byte[nread];

                for (int i = 0; i < nread; i++) {
                    trimbuf[i] = inbuf[i];
                }

                // encrypt the buffer using the cipher obtained previosly
                byte[] tmpBuf = mEcipher.update(trimbuf);

                // I don't think this should happen, but just in case..
                if (tmpBuf != null) {
                    outputStream.write(tmpBuf);
                }
            }

            // finalize the encryption since we've done it in blocks of MAX_FILE_BUF
            byte[] finalbuf = mEcipher.doFinal();

            if (finalbuf != null) {
                outputStream.write(finalbuf);
            }

            outputStream.flush();
            inputStream.close();
            outputStream.close();
            outputStream.close();
            Db("wrote " + totalread + " encrypted bytes");
        } catch (InvalidKeyException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidParameterSpecException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchPaddingException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Read from the encrypted file (input) and turn the cipher back into cleartext. Write the cleartext buffer back out
     * to disk as (output) File.
     *
     * I left CipherInputStream in here as a test to see if I could mix it with the update() and final() methods of encrypting
     *  and still have a correctly decrypted file in the end. Seems to work so left it in.
     *
     * @param input - File object representing encrypted data on disk
     * @param output - File object of cleartext data to write out after decrypting
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public void ReadEncryptedFile(InputStream inputStream, OutputStream outputStream)
            throws IllegalBlockSizeException, BadPaddingException, IOException {
        try {
            CipherInputStream cin;
            long              totalread = 0;
            int               nread     = 0;
            byte[]            inbuf     = new byte[MAX_FILE_BUF];

            // Read the Salt
            inputStream.read(this.mSalt);
            Db("generated salt :" + byteToHex(mSalt));

            SecretKeyFactory factory = null;
            SecretKey        tmp     = null;
            SecretKey        secret  = null;

            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

            KeySpec spec = new PBEKeySpec(mPassword.toCharArray(), mSalt, ITERATIONS, KEYLEN_BITS);

            tmp    = factory.generateSecret(spec);
            secret = new SecretKeySpec(tmp.getEncoded(), "AES");

            /* Decrypt the message, given derived key and initialization vector. */
            mDecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

            // Set the appropriate size for mInitVec by Generating a New One
            AlgorithmParameters params = mDecipher.getParameters();

            mInitVec = params.getParameterSpec(IvParameterSpec.class).getIV();

            // Read the old IV from the file to mInitVec now that size is set.
            inputStream.read(this.mInitVec);
            Db("mInitVec is :" + byteToHex(mInitVec));
            mDecipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(mInitVec));

            // creating a decoding stream from the FileInputStream above using the cipher created from setupDecrypt()
            cin = new CipherInputStream(inputStream, mDecipher);

            while ((nread = cin.read(inbuf)) > 0) {
                Db("read " + nread + " bytes");
                totalread += nread;

                // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
                byte[] trimbuf = new byte[nread];

                for (int i = 0; i < nread; i++) {
                    trimbuf[i] = inbuf[i];
                }

                // write out the size-adjusted buffer
                outputStream.write(trimbuf);
            }

            outputStream.flush();
            cin.close();
            inputStream.close();
            outputStream.close();
            Db("wrote " + totalread + " encrypted bytes");
        } catch (Exception ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * adding main() for usage demonstration. With member vars, some of the locals would not be needed
     */
    public static void main(String[] args) {

        // create the input.txt file in the current directory before continuing
        File   input   = new File("input.txt");
        File   eoutput = new File("encrypted.aes");
        File   doutput = new File("decrypted.txt");
        String iv      = null;
        String salt    = null;
        AES    en      = new AES("mypassword");

        /*
         * write out encrypted file
         */
        try {
            en.WriteEncryptedFile(new FileInputStream(input), new FileOutputStream(eoutput));
            System.out.printf("File encrypted to " + eoutput.getName() + "\niv:" + iv + "\nsalt:" + salt + "\n\n");
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }

        /*
         * decrypt file
         */
        AES dc = new AES("mypassword");

        /*
         * write out decrypted file
         */
        try {
            dc.ReadEncryptedFile(new FileInputStream(eoutput), new FileOutputStream(doutput));
            System.out.println("decryption finished to " + doutput.getName());
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }
    }
}

1
Bu çözüm, temelde onları günlüğe kaydedip daha sonra unutarak bazı garip tampon işleme ve kesinlikle par. CBC kullanmanın dosyalar için uygun olduğunu, ancak taşıma güvenliği için uygun olmadığını unutmayın. PBKDF2 ve AES kullanımı elbette savunulabilir, bu anlamda bir çözüm için iyi bir temel olabilir.
Maarten Bodewes

1

(Benzer bir şartı olan başkaları için de yararlı olabilir)

AES-256-CBCJava'da şifreleme ve şifre çözme kullanmak için benzer bir gereksinimim vardı .

256 baytlık şifreleme / şifre çözme işlemini gerçekleştirmek (veya belirtmek) için, Java Cryptography Extension (JCE)politika"Unlimited"

(JDK için) veya (JRE için ) java.securityaltındaki dosyada ayarlanabilir$JAVA_HOME/jre/lib/security$JAVA_HOME/lib/security

crypto.policy=unlimited

Veya koddaki gibi

Security.setProperty("crypto.policy", "unlimited");

Java 9 ve sonraki sürümlerinde bu varsayılan olarak etkindir.


0

Yazar olduğum Encryptor4j'yi kullanmayı düşünün .

256 bit AES anahtarlarını kullanabilmek için, devam etmeden önce Sınırsız Güç Yetkisi Politikası dosyalarının kurulu olduğundan emin olun .

Ardından aşağıdakileri yapın:

String password = "mysupersecretpassword"; 
Key key = KeyFactory.AES.keyFromPassword(password.toCharArray());
Encryptor encryptor = new Encryptor(key, "AES/CBC/PKCS7Padding", 16);

Artık mesajınızı şifrelemek için şifreleyiciyi kullanabilirsiniz. İsterseniz akış şifrelemesi de yapabilirsiniz. Size kolaylık sağlamak için otomatik olarak güvenli bir IV oluşturur ve hazırlar.

Sıkıştırmak istediğiniz bir dosya ise bu cevaba bir göz atın Daha basit bir yaklaşım için JAVA kullanarak AES ile büyük bir dosyayı şifreleme .


2
Merhaba Martin, eğer işaret etmek istiyorsanız daima kütüphanenin yazarı olduğunuzu belirtmelisiniz. İşleri kolaylaştırmaya çalışan kripto paketlerinin bir sürü var. Bu bir güvenlik belgesi var mı veya bizim süre değer yapmak için herhangi bir inceleme aldı mı?
Maarten Bodewes

-1

Şifreleme için bu sınıfı kullanın. İşe yarıyor.

public class ObjectCrypter {


    public static byte[] encrypt(byte[] ivBytes, byte[] keyBytes, byte[] mes) 
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = null;
        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(mes);

    }

    public static byte[] decrypt(byte[] ivBytes, byte[] keyBytes, byte[] bytes) 
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException, ClassNotFoundException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(bytes);

    }
}

Ve bunlar ivBytes ve rastgele bir anahtar;

String key = "e8ffc7e56311679f12b6fc91aa77a5eb";

byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
keyBytes = key.getBytes("UTF-8");

10
"işe yarıyor" .... evet, ancak kriptografik olarak güvenli bir çözüm oluşturmak için gereksinimleri karşılamıyor (bence istisna işleme konusunda Java kodlama standartlarını da karşılamıyor).
Maarten Bodewes

2
IV sıfıra başlatılır. BEAST ve ACPA saldırılarını arayın.
Michele Giuseppe Fadda

Wazoo istisnaları, "rastgele" anahtar üretme yöntemi ve sıfır IV bu uygulama ile ilgili bir sorundur, ancak bu sorunları düzeltmek önemsizdir. +1.
Phil
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.