PDA

View Full Version : سوال: محافظت و ایمن سازی دیتا و سیو برنامه



dasssnj
پنج شنبه 02 بهمن 1393, 14:11 عصر
سلام . یه مبحث مهم که به نظرم توی ساخت بازی و برنامه اهمیت داره ذخیره ی اطلاعات و دیتا است .

فزض کنید ما یه کد (مثلا یه آی دی منحصر به فرد) برای هر گوشی یا پی سی داریم . حالا می خوایم دیتا را بر اساس اون کد محافظت کنیم و مثلا عدد امتیاز کاربر را مبهم کنیم و ذخیره کنیم و هنگام اجرا باز گردانیش کنیم . طوری که نشه با تکست ادیتور دستکاریش کرد و با انتقال فایل دیتا به دستگاه دیگه نشه ازش استفاده کرد . یه راهش به نظرم استفاده java.security و یا base64 بود اما در این دو اطلاعات کافی ندارم .

از دوستان می خوام نظراتشون رو در مورد محافظت از دیتای برنامه بگن و هر راهی به ذهنشون می رسه در میون بزارن .

تشکر .

ahmad.mo74
پنج شنبه 02 بهمن 1393, 19:53 عصر
سلام

برای نگه داشتن یه سری اطلاعات برنامه مثل تنظیمات و سیو و ... و استفاده از اون ها برای دفعات بعدی میتونی از کلاس java.util.prefs.Preferences استفاده کنی. فرض کنیم مثلا یه برنامه ادیتور نوشتی و میخوای مسیر آخرین فایلی که کاربر باز کرده بوده رو یه جا نگه داری تا دفعه بعدی که برنامه باز میشه دوباره اون فایل رو برای کاربر باز کنه.


Preferences preferences = Preferences.userNodeForPackage(Test.class);
preferences.put("lastOpenedFile", "D:\\a.txt");


و برای خوندنش :


Preferences preferences = Preferences.userNodeForPackage(Test.class);
String lastOpenedFile = preferences.get("lastOpenedFile", null);
System.out.println(lastOpenedFile);


این اطلاعات برای unix تو فولدر etc/.java و فولدر هوم یوزر ذخیره میشه و برای ویندوز هم توی رجیستری ویندوز ذخیره میشه.

http://docs.oracle.com/javase/7/docs/api/java/util/prefs/Preferences.html
http://docs.oracle.com/javase/8/docs/technotes/guides/preferences/index.html

به جای Preferences هم میتونی اطلاعاتی که میخوای رو توی محل مشخصی مثل فولدر هوم یوزر به صورت xml یا هر جور دیگه ای ذخیره کنی و هروقت خواستی ازش استفاده کنی.
برای گرفتن مسیر هوم یوزر :


System.getProperty("user.home");


تا اینجا تقریبا نیازت برطرف میشه و فقط مونده رمزگزاری فایل ها و رشته ها. میشه از MD5 و SHA استفاده کرد اما اینا برگشت پذیر نیستن و به جاش میتونی از AES یا RSA استفاده کنی.
برای رشته ها :

AES :


import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;


/**
* @author avb
*/
public class AESEncryption {


private final static String HEX = "0123456789ABCDEF";


public static String encrypt(String text, String key) {
try {
byte[] rawKey = getRawKey(key.getBytes());
byte[] result = encrypt(rawKey, text.getBytes());
return toHex(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}


public static String decrypt(String encrypted, String key) {
try {
byte[] rawKey = getRawKey(key.getBytes());
byte[] enc = toByte(encrypted);
byte[] result = decrypt(rawKey, enc);
return new String(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}


private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(seed);
keyGenerator.init(128, sr);
SecretKey secretKey = keyGenerator.generateKey();
return secretKey.getEncoded();
}


private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher.doFinal(clear);
}


private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
return cipher.doFinal(encrypted);
}


private static byte[] toByte(String hex) {
int len = hex.length() / 2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
result[i] = Integer.valueOf(hex.substring(2 * i, 2 * i + 2), 16).byteValue();
}
return result;
}


private static String toHex(byte[] raw) {
if (raw == null) {
return null;
}
StringBuilder hex = new StringBuilder(2 * raw.length);
for (byte b : raw) {
hex.append(HEX.charAt(b >> 4 & 0xf)).append(HEX.charAt(b & 0x0f));
}
return hex.toString();
}


public static void main(String[] args) {
String key = "this is a key";
String encrypted = encrypt("abc", key);
System.out.println(encrypted);
System.out.println(decrypt(encrypted, key));
}


}


MD5 و SHA :


import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;


/**
* @author avb
*/
public final class EncodingUtil {


private final static String HEX = "0123456789ABCDEF";


public static String getSHA256(String text) {
return encode(text, "SHA-256");
}


public static String getMD5(String text) {
return encode(text, "MD5");
}


private static String encode(String text, String algorithm) {
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(text.getBytes());
return toHex(md.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}


private static String toHex(byte[] raw) {
if (raw == null) {
return null;
}
StringBuilder hex = new StringBuilder(2 * raw.length);
for (byte b : raw) {
hex.append(HEX.charAt(b >> 4 & 0xf)).append(HEX.charAt(b & 0x0f));
}
return hex.toString();
}


public static void main(String[] args) {
System.out.println(getSHA256("abc"));
System.out.println(getMD5("abc"));
}


}


برای فایل ها :

http://www.codejava.net/coding/file-encryption-and-decryption-simple-example
http://www.macs.hw.ac.uk/~ml355/lore/pkencryption.htm
http://www.avajava.com/tutorials/lessons/how-do-i-encrypt-and-decrypt-files-using-des.html

برای اینکه متوجه بشی که فایل هات تغییر کرده یا نه میتونی hash فایل رو تو یه فایل جداگانه دیگه ای نگه داری و هربار که فایل رو باز میکنی با اون مقایسه کنی اگه یکسان نباشه یعنی که فایل دستکاری شده و میتونی به کاربر اخطار بدی یا اجرا متوقف کنی یا...

در ضمن اگر بتونی برای برنامت سرور داشته باشی و این اطلاعات رو تو سرور ذخیره کنی و از سرور بگیریشون دیگه درگیر این چیزا نمیشی و فقط برای کش کردن یه سری اطلاعات و ستینگ هایی که از لحاظ امنیتی خیلی مهم نباشه، توی کامپیوتر یا گوشی کاربر ذخیره کنی. مثل وایبر و واتس اپ که هیستوری چت، عکس پروفایل و... تو دیوایس کاربر کش میکنن.

dasssnj
جمعه 03 بهمن 1393, 05:02 صبح
ممنون . تقریبا به جوابم خیلی نزدیک شدم اما محافظت از خود اون کلید رمز نگاری چون جاوا راحت دیکامپایل میشه ، خودش دردسر ساز میشه . حتی اگه کلید را در زمان اجرا تولید کنم باز هم با دیکامپایل و کپی کردن اون تابع میشه ازش خروجی گرفت . :گریه: راهی نیست که بشه تا حدی این مشکل را برطرف کرد ؟

dasssnj
پنج شنبه 09 بهمن 1393, 16:00 عصر
همه چیز درست عمل می کنه اما زمانی رشته ی کد شده را از فایل می خونم و می خوام دیکدش کنم این ارور را میده :


01-29 16:31:16.176: W/System.err(31586): javax.crypto.BadPaddingException: pad block corrupted
01-29 16:31:16.186: W/System.err(31586): at com.android.org.bouncycastle.jcajce.provider.symme tric.util.BaseBlockCipher.engineDoFinal(BaseBlockC ipher.java:739)
01-29 16:31:16.186: W/System.err(31586): at javax.crypto.Cipher.doFinal(Cipher.java:1204)

dasssnj
پنج شنبه 09 بهمن 1393, 20:03 عصر
زمانی که برای بازی بسته میشه و دوباره باز میشه دیکد اشتباه میشه چون کلید رندم عوض میشه . برای encrypt کردن توی این حالت چه کاری باید کرد ؟
(الگوریتم RSA را هم اگه میشه توضیح بدید)

ahmad.mo74
جمعه 10 بهمن 1393, 16:37 عصر
سلام. بهترین روشی که به ذهنم رسید (اگر از سرور نخوای استفاده کنی) استفاده ترکیبی از AES و RSA بود.
RSA (http://en.wikipedia.org/wiki/RSA_(cryptosystem)) یک روش public-key (http://en.wikipedia.org/wiki/Public-key_cryptography) برای رمزگزاری هست. توی درس ساختمان های گسسته هم مفصل میخونید دربارش.

الان ما نیاز به یک AES Key داریم که فایل هارو با اون کلید encrypt و decrypt کنیم.
اما مشکل نگه داری کردن این key هست و اینکه دست کسی نیفته. خب اگر از سرور استفاده کنیم کارمون خیلی راحت میشه چون key رو از سرور میگیریم.

روشی که به ذهنم رسید این بود که با روش RSA با استفاده از public key اون key اصلی رو encrypt کنیم و در جایی ذخیره کنیم.
بعد که کلید رو خواستیم با private key اون رو decrypt کنیم. تا اینجا از key اصلی میشه محافظت کرد و اگر کسی هم اونو پیدا کنه نمیتونه کاری کنه چون encrypt شده.
اما الان مشکل ذخیره کردن اون public key و private key هست :))

در این حالت امن ترین روش اینه که هر 3 تارو (aes key, public key, private key) رو با کلاس Preferences ذخیره کنیم. اما به هر حال امن ترین روش استفاده از server هست.

میشه یه کاری کرد که این فرایند پیچیده تر بشه مثلا خود این key هارم encode کنیم یا مثلا دوباره private key رو هم با یه key دیگه ای encrypt کنیم و... که فهمیدنش برای بقیه سخت بشه...

FileEncryption :


package com.security.file;


import org.apache.commons.codec.binary.Base64;


import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.prefs.Preferences;


/**
* @author avb
*/
public final class FileSecurity {


private static final String AES_ALGORITHM = "AES";
private static final String RSA_ALGORITHM = "RSA";


private static final int AES_KEY_SIZE = 128;
private static final int RSA_KEY_SIZE = 2048;


private final Cipher aesCipher;
private SecretKeySpec aesKeySpec;


public FileSecurity() throws Exception {
aesCipher = Cipher.getInstance(AES_ALGORITHM);
loadKeys();
}


private void loadKeys() throws Exception {
Preferences preferences = Preferences.userNodeForPackage(FileSecurity.class) ;
String aes_pri_key = preferences.get("aes_pri_key", null);
String rsa_pri_key = preferences.get("rsa_pri_key", null);
String rsa_pub_key = preferences.get("rsa_pub_key", null);
RSAPrivateKeySpec rsaPrivateKeySpec;
RSAPublicKeySpec rsaPublicKeySpec;
if (rsa_pri_key == null || rsa_pub_key == null) {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyPairGenerator.initialize(RSA_KEY_SIZE);
KeyPair keyPair = keyPairGenerator.genKeyPair();
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
rsaPrivateKeySpec = keyFactory.getKeySpec(keyPair.getPrivate(), RSAPrivateKeySpec.class);
rsaPublicKeySpec = keyFactory.getKeySpec(keyPair.getPublic(), RSAPublicKeySpec.class);
preferences.put("rsa_pri_key"
, Base64.encodeBase64String((rsaPrivateKeySpec.getMo dulus() + "/" + rsaPrivateKeySpec.getPrivateExponent()).getBytes() ));
preferences.put("rsa_pub_key"
, Base64.encodeBase64String((rsaPublicKeySpec.getMod ulus() + "/" + rsaPublicKeySpec.getPublicExponent()).getBytes())) ;
} else {
String[] ss = new String(Base64.decodeBase64(rsa_pri_key)).split("/");
rsaPrivateKeySpec = new RSAPrivateKeySpec(new BigInteger(ss[0]), new BigInteger(ss[1]));
ss = new String(Base64.decodeBase64(rsa_pub_key)).split("/");
rsaPublicKeySpec = new RSAPublicKeySpec(new BigInteger(ss[0]), new BigInteger(ss[1]));
}
Cipher rsaCipher = Cipher.getInstance(RSA_ALGORITHM);
if (aes_pri_key == null) {
KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
keyGenerator.init(AES_KEY_SIZE);
byte[] aesKey = keyGenerator.generateKey().getEncoded();
aesKeySpec = new SecretKeySpec(aesKey, AES_ALGORITHM);
rsaCipher.init(Cipher.ENCRYPT_MODE, KeyFactory.getInstance(RSA_ALGORITHM).generatePubl ic(rsaPublicKeySpec));
preferences.put("aes_pri_key", Base64.encodeBase64String(rsaCipher.doFinal(aesKey )));
} else {
rsaCipher.init(Cipher.DECRYPT_MODE, KeyFactory.getInstance(RSA_ALGORITHM).generatePriv ate(rsaPrivateKeySpec));
aesKeySpec = new SecretKeySpec(rsaCipher.doFinal(Base64.decodeBase6 4(aes_pri_key)), AES_ALGORITHM);
}
}


public void resetKeys() throws Exception {
Preferences preferences = Preferences.userNodeForPackage(FileSecurity.class) ;
preferences.remove("aes_pri_key");
preferences.remove("rsa_pri_key");
preferences.remove("rsa_pub_key");
loadKeys();
}


public void encrypt(Path source, Path target) throws Exception {
doCipher(Cipher.ENCRYPT_MODE, source, target);
}


public void decrypt(Path source, Path target) throws Exception {
doCipher(Cipher.DECRYPT_MODE, source, target);
}


private synchronized void doCipher(int mode, Path source, Path target) throws Exception {
aesCipher.init(mode, aesKeySpec);
try (FileInputStream fileInputStream = new FileInputStream(source.toFile());
FileChannel inChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream(target.toFile());
CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, aesCipher)) {
ByteBuffer buffer = ByteBuffer.allocate(64 * 1024);
int read;
while (true) {
read = inChannel.read(buffer);
if (read == -1) {
break;
}
buffer.flip();
cipherOutputStream.write(buffer.array(), 0, read);
buffer.clear();
}
}
}


public static void main(String[] args) throws Exception {
FileSecurity fileSecurity = new FileSecurity();
fileSecurity.encrypt(Paths.get("D:\\a.mp3"), Paths.get("D:\\b"));
fileSecurity.decrypt(Paths.get("D:\\b"), Paths.get("D:\\c.mp3"));
}


}



apache-commons-codec (http://commons.apache.org/proper/commons-codec/) رو هم به lib اضافه کن.

اگر خواستی از کلید 256 بیتی استفاده کنی اینارو ببین :

http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
http://stackoverflow.com/questions/6481627/java-security-illegal-key-size-or-default-parameters/6481658#6481658

dasssnj
جمعه 10 بهمن 1393, 16:46 عصر
ممنون . همون روش قبلی خوب بود این یکی هم یه مقدار پیچیده بود و هم برای فایل ها بود . من یه سره از خود بازی دیتا را سیو می کنم و نیازی به اینکه توی یه فایل سیو کنم و بعد اونو کد گزاری کنم نیست . اون روش قبلی تنها مشکلش این بود که وقتی بازی بسته میشد و دوباره باز میشد نمی تونست decrypt کنه . اگه راه حلی برای اون هست ممنون میشم بگید وگرنه باید یه فکری برای روش دوم بکنم .

ahmad.mo74
جمعه 10 بهمن 1393, 16:56 عصر
نه دیگه راهی نیست.تو روش AES وقتی با یه کلید encrypt کنی با همون کلید هم باید decrypt کنی، من نمیدونم دقیقا چیکار کردی، ولی یا باید از یک کلید استفاده کنی یا اینکه اگر کلید جدید تولید میکنی یه جوری باید مشخص کنی که هر کلید برای کدوم دیتا هست.

dasssnj
جمعه 10 بهمن 1393, 18:06 عصر
مشکل کلید من نیست . مشکل اون SecureRandom هست که هر دفعه یه چیزی تولید می کنه و با دفعه ی قبلی اجرای بازی تفاوت داره و نمی تونه دیکد کنه . فکر کنم اندروید کلا با AES مشکل داره

ahmad.mo74
جمعه 10 بهمن 1393, 21:39 عصر
نه ربطی نداره، به اون کلیدی که بهش میدی ربط داره. حالا کدتو بزار ببینم مشکل کجاست.

dasssnj
شنبه 11 بهمن 1393, 05:05 صبح
نه ربطی نداره، به اون کلیدی که بهش میدی ربط داره. حالا کدتو بزار ببینم مشکل کجاست.

کد هم طولانیه و هم مهمه برام . براتون پیام خصوصی می کنم .

ahmad.mo74
شنبه 25 بهمن 1393, 12:57 عصر
سلام

یک روش نسبتا امن تر و بهتری برای اینکار به ذهنم رسید.

کلاس AESEncryption رو کلا تغییر دادم و روش قوی تری برای encrypt کردن پیدا کردم.

تو این روش به جای انکریپت کردن مستقیم یک رشته، یک آبجکت رو انکریپت میکنیم که کمترین مزیتش اینه که هر آبجکتی بخوای میتونی باهاش انکریپت کنی و دوباره راحت اون آبجکت رو برگردونی و این کار رو برامون خیلی راحت میکنه.
ولی بازم باید از یک کلیدی استفاده بشه، که امنیت اون کلید و نگه داریش دست خودته... میتونی تو یه فایلی یا تو Preferences یا سرور ... دخیرش کنی.

AESEncryption :


package com.util.security;


import org.apache.commons.codec.binary.Base64;


import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;


/**
* @author avb
*/
public final class AESEncryption {


private static final int ITERATION_COUNT = 1000;
private static final int KEY_LENGTH = 128;
private static final int SALT_LENGTH = KEY_LENGTH / 8;


public static String encrypt(byte[] bytes, String key) {
try {
SecureRandom secureRandom = new SecureRandom();
byte[] salt = new byte[SALT_LENGTH];
secureRandom.nextBytes(salt);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize()];
secureRandom.nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key, salt), new IvParameterSpec(iv));
byte[] cipherBytes = cipher.doFinal(bytes);
return Base64.encodeBase64String(salt) + "]" + Base64.encodeBase64String(iv) + "]" + Base64.encodeBase64String(cipherBytes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}


public static String encrypt(String plainText, String key) {
return encrypt(plainText.getBytes(StandardCharsets.UTF_8) , key);
}


public static byte[] decrypt(String encryptedText, String key) {
try {
String[] ss = encryptedText.split("]");
byte[] salt = Base64.decodeBase64(ss[0]);
byte[] iv = Base64.decodeBase64(ss[1]);
byte[] cipherBytes = Base64.decodeBase64(ss[2]);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key, salt), new IvParameterSpec(iv));
return cipher.doFinal(cipherBytes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}


public static String decryptToString(String encryptedText, String key) {
return new String(decrypt(encryptedText, key), StandardCharsets.UTF_8);
}


private static SecretKey getSecretKey(String key, byte[] salt) throws Exception {
byte[] keyBytes = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
.generateSecret(new PBEKeySpec(key.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH))
.getEncoded();
return new SecretKeySpec(keyBytes, "AES");
}


}


SecureReference :


package com.util.security;


import java.io.Serializable;


/**
* @author avb
*/
public final class SecureReference<R extends Serializable> implements Serializable {


private static final long serialVersionUID = -1298206853491951768L;


private R r;


public SecureReference() {
}


public SecureReference(R r) {
this.r = r;
}


public R get() {
return r;
}


public void set(R r) {
this.r = r;
}


@Override
public String toString() {
return String.valueOf(get());
}


}


SecureReferenceMapper :


package com.util.security;


import java.io.*;


/**
* @author avb
*/
public final class SecureReferenceMapper {


public static String mapToString(String key, SecureReference reference) throws IOException {
try (ByteArrayOutputStream aos = new ByteArrayOutputStream()) {
try (ObjectOutputStream oos = new ObjectOutputStream(aos)) {
oos.writeObject(reference);
}
return AESEncryption.encrypt(aos.toByteArray(), key);
}
}


@SuppressWarnings("unchecked")
public static <R extends Serializable> SecureReference<R> mapToReference(String key, String value) throws IOException, ClassNotFoundException {
byte[] bytes = AESEncryption.decrypt(value, key);
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes, 0, bytes.length))) {
return (SecureReference<R>) ois.readObject();
}
}


}


مثال :

Score :


package test;


import java.io.Serializable;


/**
* @author avb
*/
public class Score implements Serializable {


private static final long serialVersionUID = 5582085734190782886L;


private int score;


public int getScore() {
return score;
}


public void setScore(int score) {
this.score = score;
}


}


Main :


package test;


import com.util.security.SecureReference;
import com.util.security.SecureReferenceMapper;


import java.io.IOException;


/**
* @author avb
*/
public class Main {


public static void main(String[] args) throws IOException, ClassNotFoundException {
SecureReference<Score> score = new SecureReference<>(new Score());
score.get().setScore(20);
String string = SecureReferenceMapper.mapToString("abc", score);
System.out.println(string);
SecureReference<Score> reference = SecureReferenceMapper.mapToReference("abc", string);
System.out.println(reference.get().getScore());
}


}


حتی اگرم کلید لو بره، اگر طرف ندونه چیکار کردی کار براش سخت میشه چون باید آبجکت رو deserialize کنه و ...

(در ضمن این روش برای اندروید هم جواب میده)