Based on recent feedback and findings on this problem, I've rewritten the question to get rid of noise.
I have 2 separate code paths, one in Java (Android), one and Python which accomplish the following for the purposes of negotiating a pairing between an Android device and a Python/Django.
Java:
Generate a syncKey
Hash a concatenated string of various values using the presharedKey (including the syncKey)
Encrypt the syncKey using a presharedKey
Send the Hash, encrypted syncKey, DeviceId and arbitrary variables to web server
Python
Get the presharedKey from the deviceId
Decrypt the encrypted syncKey
Hash a concatenated string of various values using the presharedKey (including the decrypted syncKey)
Make sure the hash matches, which confirms that the syncKey was decrypted successfully, and that the deviceId holds the correct presharedKey.
Now this process works if I send the syncKey unencrypted. The final hash matches, which proves the deviceId has the correct preshared-key, however as soon as I add the en/decryption into the process, the hash no longer matches, despite the fact that both the syncKey and concatenated string appear to match perfectly character for character from the debug output of both Java/Python.
One quirk of the process is that a 256bit key is necessary for the AES256 encryption algorithm, so I'm chopping the 512bit presharedKey in half. The alternative of using only a 256bit key across the board was requiring that I pass the key through encode('ascii') on the python side, or else it was throwing up errors during hashing with the shorter key.
Here is the relevant code:
Java:
String presharedKey = getKey();
// f8250b0d5960444e4de6ecc3a78900bb941246a1dece7848fc72b90092ab3ecd0c1c8e36fddba501ef92e72c95b47e07f98f7fd9cb63da75c008a3201124ea5d
String deviceId = getDeviceId();
// 1605788742789230
SyncKey syncKey = generateSyncKey();
// 824C1EE9EF507B52EA28362C71BD4AD512A5F82ACFAE80DEF531F73AC124CA814BA30CE805A157D6ADB9EC04FC99AAE6FDC4238FCD76B87CE22BC2FE33B2E5C9
String concat = syncKey.hexString();
// 824C1EE9EF507B52EA28362C71BD4AD512A5F82ACFAE80DEF531F73AC124CA814BA30CE805A157D6ADB9EC04FC99AAE6FDC4238FCD76B87CE22BC2FE33B2E5C9
String ALGORITHM = "HmacSHA256";
String hash = null;
try {
SecretKeySpec keySpec = new SecretKeySpec(
presharedKey.getBytes(),
ALGORITHM);
Mac mac = Mac.getInstance(ALGORITHM);
mac.init(keySpec);
byte[] result = mac.doFinal(concat.getBytes());
hash = Base64.encodeToString(result, Base64.DEFAULT);
// FpDE2JLmCBr+/rW+n/jBHH13F8AV80sUM2fQAY2IpRs=
} catch (NoSuchAlgorithmException x) {
} catch (InvalidKeyException x) {
}
String encKey = presharedKey.substring(0, presharedKey.length() / 2);
// f8250b0d5960444e4de6ecc3a78900bb941246a1dece7848fc72b90092ab3ecd
int len = encKey.length();
byte[] encKeyBytes = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
encKeyBytes[i / 2] = (byte) ((Character.digit(encKey.charAt(i), 16) << 4)
+ Character.digit(encKey.charAt(i+1), 16));
}
String encryptedSyncKey = null;
try {
byte[] iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec encKeySpec = new SecretKeySpec(encKeyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, encKeySpec, ivSpec);
byte[] encryptedSyncKeyBytes = cipher.doFinal(syncKey.hexString().getBytes());
encryptedSyncKey = Base64.encodeToString(encryptedSyncKeyBytes, Base64.DEFAULT);
/*
Yrl0/SuTUUTC6oJ8o4TCOy65EwO0JzoXfEi9kLq0AOlf6rH+nN7+BEc0s5uE7TIo1UlJb/DvR2Ca
ACmQVXXhgpZUTB4sQ0eSo+t32lg0EEb9xKI5CZ4l9QO5raw0xBn7r/tfIdVm8AIFkN9QCcthS0DF
KH3oWhpwNS+tfEuibLPgGqP/zGTozmido9U9lb4n
*/
} catch (InvalidAlgorithmParameterException e) {
} catch (NoSuchAlgorithmException e) {
} catch (NoSuchPaddingException e) {
} catch (InvalidKeyException e) {
} catch (IllegalBlockSizeException e) {
} catch (BadPaddingException e) {
}
sendStuffToWeb(encryptedSyncKey, deviceId, hash);
Python:
hash = getHash(request)
# hash from Java: FpDE2JLmCBr+/rW+n/jBHH13F8AV80sUM2fQAY2IpRs=
encrypted_sync_key = getEncSyncKey(request)
# encryptedSyncKey from Java:
# Yrl0/SuTUUTC6oJ8o4TCOy65EwO0JzoXfEi9kLq0AOlf6rH+nN7+BEc0s5uE7TIo1UlJb/DvR2Ca
# ACmQVXXhgpZUTB4sQ0eSo+t32lg0EEb9xKI5CZ4l9QO5raw0xBn7r/tfIdVm8AIFkN9QCcthS0DF
# KH3oWhpwNS+tfEuibLPgGqP/zGTozmido9U9lb4n
device_id = getDeviceId(request)
# 1605788742789230
preshared_key = getPresharedKeyFromDevice(deviceId)
# f8250b0d5960444e4de6ecc3a78900bb941246a1dece7848fc72b90092ab3ecd0c1c8e36fddba501ef92e72c95b47e07f98f7fd9cb63da75c008a3201124ea5d
enc_key = preshared_key[:len(preshared_key)/2]
# f8250b0d5960444e4de6ecc3a78900bb941246a1dece7848fc72b90092ab3ecd
aes = AES.new(enc_key.decode('hex'), AES.MODE_CBC, IV="\x00"*16)
sync_key = aes.decrypt(base64.b64decode(encrypted_sync_key))
# 824C1EE9EF507B52EA28362C71BD4AD512A5F82ACFAE80DEF531F73AC124CA814BA30CE805A157D6ADB9EC04FC99AAE6FDC4238FCD76B87CE22BC2FE33B2E5C9
concat = sync_key
# 824C1EE9EF507B52EA28362C71BD4AD512A5F82ACFAE80DEF531F73AC124CA814BA30CE805A157D6ADB9EC04FC99AAE6FDC4238FCD76B87CE22BC2FE33B2E5C9
import hashlib
from hmac import new as hmac
verify_hash = hmac(preshared_key, concat, hashlib.sha256).digest().encode('base64')
# IoSc2w2sQ4/fwhJTdUQHw/Hdyjy+ranzQ1z3J5LfYbA=
From the debug output below you can see the syncKey is encrypted and decrypted successfully, and the concat is identical. However the resulting hash ends up being different.
Your Python code is wrong. I can reproduce, in Python, the answer you got in Java.
If I use your inputs:
>>> preshared_key_hex
b'f8250b0d5960444e4de6ecc3a78900bb941246a1dece7848fc72b90092ab3ecd0c1c8e36fddba501ef92e72c95b47e07f98f7fd9cb63da75c008a3201124ea5d'
>>> concat_hex
b'824C1EE9EF507B52EA28362C71BD4AD512A5F82ACFAE80DEF531F73AC124CA814BA30CE805A157D6ADB9EC04FC99AAE6FDC4238FCD76B87CE22BC2FE33B2E5C9'
I get the same value you get in Java:
>>> base64.b64encode(hmac.new(preshared_key_hex, concat_hex, hashlib.sha256).digest())
b'FpDE2JLmCBr+/rW+n/jBHH13F8AV80sUM2fQAY2IpRs='
However, that value is probably also wrong. You should almost certainly be hex decoding the input values.
I'm unable to reproduce what you got in Python; one of the values you're passing to hmac.new isn't what you think it is. print them immediately before calling hmac.new and you should see what doesn't match.
Related
I am trying to create a program in java in which part of it uses AES encryption to encrypt data for my final project in a coding class. Here is the code that I am using for my encryption:
static String symmetric(String info, String key, String mode) {
try {
Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding");
byte [] bytes = Base64.getDecoder().decode(Crypto.sha256(key));
byte [] information = Base64.getDecoder().decode(info);
Key k = new SecretKeySpec(bytes, "AES");
if (mode.equals("ENCRYPT")) {
c.init(Cipher.ENCRYPT_MODE, k);
} else if (mode.equals("DECRYPT")) {
c.init(Cipher.DECRYPT_MODE, k);
}
return (Base64.getEncoder().encodeToString(c.doFinal(information)).trim());
} catch (Exception e) {
JOptionPane.showMessageDialog(null, e.getMessage());
}
return (null);
}
When I encrypt my data using String ciphterText = symmetric("message", "key", "ENCRYPT") and decrypt the ciphertext using symmetric(cipherText, "key", "DECRYPT"), the string it returns is "messagc=". I'm worried that the padding is weird but I don't know how to fix it.
FYI: Crypto.sha256(String input) is a method I created that returns the sha256 hash of info as a base 64 string. Here is the code for it if it helps:
public static String sha256(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte [] tempHash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
return (Base64.getEncoder().encodeToString(tempHash));
} catch (NoSuchAlgorithmException e) {
JOptionPane.showMessageDialog(null, e.getMessage());
}
return (null);
}
Also I know ECB is not secure compared to other methods that use initialization vectors, but it is a small project and I don't have enough time to do that, which is the same reason why I'm not salting my hashes. Is there anything I can do to fix it?
This is a problem with the way you are using base-64 encoding.
When you encrypt, you are treating "message" as base-64 encoded bytes. The last block is "age". A strict decoder would reject that input, because it is missing padding, and has some extra bits that spill over into the third byte. But a permissive decoder ignores that, and decodes the array as { 0x99, 0xeb, 0x2c, 0x6a, 0x07 }
The correct base-64 encoding of { 0x99, 0xeb, 0x2c, 0x6a, 0x07 } is "messagc=".
To make this work correctly, every statement in your method should differ depending on the mode flag. It would be more clear and clean to separate encrypt and decrypt methods.
I'm currently running into an issue where our decryption portion of our C# site is having trouble with the padding with the encrypted string from java. The .Net code throws this error "Padding is invalid and cannot be removed". The _signKey and _encKey are both 64 bytes.
public String encryptString(String plainText) {
byte[] ciphertext;
byte[] iv = new byte[16];
byte[] plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
String _signKey = "****************************************************************";
String _encKey = "****************************************************************";
try {
Mac sha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec shaKS = new SecretKeySpec(_signKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256.init(shaKS);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
byte[] sessionKey = sha256.doFinal((_encKey + iv).getBytes(StandardCharsets.UTF_8));
// Perform Encryption
SecretKeySpec eks = new SecretKeySpec(sessionKey, "AES");
cipher.init(Cipher.ENCRYPT_MODE, eks, ivParams);
ciphertext = cipher.doFinal(plainBytes);
System.out.println("ciphertext= " + new String(ciphertext));
// Perform HMAC using SHA-256 on ciphertext
SecretKeySpec hks = new SecretKeySpec(_signKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(hks);
ByteArrayOutputStream outputStream2 = new ByteArrayOutputStream();
outputStream2.write(iv);
outputStream2.write(ciphertext);
outputStream2.flush();
outputStream2.write(mac.doFinal(outputStream2.toByteArray()));
return Base64.encodeBase64String(outputStream2.toByteArray());
} catch (Exception e) {
e.printStackTrace();
}
return plainText;
}
Does does encrypt the string properly as far as I can tell. We cannot change any code on the .Net side to decrypt this because this is being used today.
public static string DecryptString(string ciphertext)
{
using (HMACSHA256 sha256 = new HMACSHA256(Encoding.UTF8.GetBytes(_signKey)))
{
// Convert message to bytes
byte[] encBytes = Convert.FromBase64String(ciphertext);
// Get arrays for comparing HMAC tags
byte[] sentTag = new byte[sha256.HashSize / 8];
byte[] calcTag = sha256.ComputeHash(encBytes, 0, (encBytes.Length - sentTag.Length));
// If message length is too small return null
if (encBytes.Length < sentTag.Length + _ivLength) { return null; }
// Copy tag from end of encrypted message
Array.Copy(encBytes, (encBytes.Length - sentTag.Length), sentTag, 0, sentTag.Length);
// Compare tags with constant time comparison, return null if no match
int compare = 0;
for (int i = 0; i < sentTag.Length; i++) { compare |= sentTag[i] ^ calcTag[i]; }
if (compare != 0) { return null; }
using (AesCryptoServiceProvider csp = new AesCryptoServiceProvider())
{
// Set parameters
csp.BlockSize = _blockBits;
csp.KeySize = _keyBits;
csp.Mode = CipherMode.CBC;
csp.Padding = PaddingMode.PKCS7;
// Copy init vector from message
var iv = new byte[_ivLength];
Array.Copy(encBytes, 0, iv, 0, iv.Length);
// Derive session key
byte[] sessionKey = sha256.ComputeHash(Encoding.UTF8.GetBytes(_encKey + iv));
// Decrypt message
using (ICryptoTransform decrypt = csp.CreateDecryptor(sessionKey, iv))
{
return Encoding.UTF8.GetString(decrypt.TransformFinalBlock(encBytes, iv.Length, encBytes.Length - iv.Length - sentTag.Length));
}
}
}
}
If there is anything that sticks out it would be appreciated for the reply.
I didn't read all your code, but this line in Java:
byte[] sessionKey = sha256.doFinal((_encKey + iv).getBytes(StandardCharsets.UTF_8));
does nothing useful or sensible. The "+" operator does string concatenation, but iv is a byte[], not a String. So java uses iv.toString(), which simply returns a String containing something like [B#1188e820 which is meaningless in this context.
Refer four java code and DotNet code:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //Java
csp.Padding = PaddingMode.PKCS7; //.Net
You are essentially using different padding, that is the probable source of error; however, there is an alternate view, Refer this great post and this for general fundamentals on padding
The cipher suites supported by deafult Oracle JVM implementation are here
If you notice it does not have 'AES/CBC/PKCS7Padding', a PKCS#7 padding implementation is available in sun.security package, refer this, otherwise you could use Bouncy Castle packages. It would be recommendable to use Bouncy Castle as com.sun package are generally considered unsupported.
Java supports three MAC algorithms:
HmacMD5
HmacSHA1
HmacSHA256
I however need to sign someting using HMAC-SHA256-128, which is HmacSHA256 but truncated to 128 bits.
This example and variants of has circulated on stackoverflow:
String MAC = hmacHelper.calculatePlainMAC("00000000", "HmacSHA256");
String bgSecretKey="1234567890ABCDEF1234567890ABCDEF";
public String calculatePlainMAC(String ascii, String algorithm)
{
Mac mac = null;
final Charset asciiCs = Charset.forName("US-ASCII");
try
{
SecretKeySpec signingKey = new SecretKeySpec(bgcSecretKey.getBytes(), algorithm);
mac = Mac.getInstance(algorithm);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(asciiCs.encode(ascii).array());
String result = "";
for (final byte element : rawHmac)
{
result += Integer.toString((element & 0xff) + 0x100, 16);//.substring(1);
}
log.debug("Result: " + result);
return result;
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
return null;
}
catch (InvalidKeyException e)
{
e.printStackTrace();
return null;
}
}
Result:
1051cd18118219e1261f41401891fd1911a91cf1bc1751db13e10617c1221131231c31ab15613f14412c1681d7132178
This is all good, except that I need a 128-bit result, which I know is
FF365893D899291C3BF505FB3175E880
I have no idea how they reached this result. What I do know is that the HMAC algorithm used is HmacSHA256-128. From what I understand this algorithm will generate a 256-bit result, question is, how do I truncate this into a 128-bits result, returning the known result above?
The following line always adds 3 characters to the string, starting with '1'. The commented substring(1) removes the 1. It is used so that single character results get a zero pre-pended.
result += Integer.toString((element & 0xff) + 0x100, 16);//.substring(1);
However, even when you fix this, the result does not contain the truncated result you are expecting.
05cd81829e26f44089fd91a9cfbc75db3e067c221323c3ab563f442c68d73278
This of course depends on the value of bgcSecretKey.
You need to use the same key/algorithm/truncation you used to derive the expected result.
The problem is that the "key" should be converted to it's binary representation, not to the binary representation of the string value!
Ie. bgcSecretKey.getBytes() should be javax.xml.bind.DatatypeConverter.parseHexBinary(bgcSecretKey) or whatever function you prefer to convert hex to binary value.
Then it all works. The whole code:
#Test
public void test_key() throws Exception
{
String SECRET = "1234567890ABCDEF1234567890ABCDEF";
Charset CHARSET = Charset.forName("ISO-8859-1");
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new javax.crypto.spec.SecretKeySpec(DatatypeConverter.parseHexBinary(SECRET), "HmacSHA256");
try
{
sha256_HMAC.init(secret_key);
}
catch (InvalidKeyException e1)
{
throw new RuntimeException("Sha key couldn't be initialized", e1);
}
//Create KVV key, it's HMAC of eight zeros
final byte[] kvv_bytes = sha256_HMAC.doFinal(CHARSET.encode("00000000").array());
StringBuilder kvv = new StringBuilder(16);
for (int i = 0; i < Math.min(16, kvv_bytes.length); i++) //KVV, max 16 bytes
{
kvv.append(Integer.toString((kvv_bytes[i] & 0xff) + 0x100, 16).substring(1));
}
System.out.println(kvv.toString().toUpperCase());
}
I'm getting an error everytime I run this
"Error: Given final block not properly padded"
Basically I'm trying to brute force the last 3 bytes of the key, the first 13 bytes are correct.
Any idea what am I doing wrong? I tried removing the padding and it works but it couldn't find the plaintext that I'm sure it exists and contains the word "Mary had".
Note: I'm using sun.misc.BASE64Decoder
here's a part of my code.
String myiv = new String(new byte[] {
0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x31,0x30,0x31,0x31,0x31,0x32,0x33
});
char [] mykeyarray = new char[] {0x86,0xe5,0x30,0x90,0xff,0x62,0xa0,0x9a,0x81,0x00,0xad,0x9e,0x8f,0x00,0x00,0x00};
String encoded = "dm8cfvs+c7pKM+WR+fde8b06SB+lqWLS4sZW+PfQSKtTfgPknzYzpTVOtJP3JBoU2Uo/7XWopjoPDOlPr24duuck0z+vAx91bYTwQo4INnIIBkj/lhJMWmvAKaUIO3qzBoGg8ynQOhuG6LY7Wo0uww==";
IvParameterSpec ivspec = new IvParameterSpec(myiv.getBytes());
byte [] decoded;
FileWriter fstream = new FileWriter("out.txt");
BufferedWriter out = new BufferedWriter(fstream);
String mykey;
int repeat = 256;
outerloop:
for(int i=0;i<repeat;i++){
for(int j=0;j<repeat;j++){
for(int k=0;k<repeat;k++){
mykey = new String(mykeyarray);
SecretKeySpec keyspec = new SecretKeySpec(mykey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
System.out.println("I: "+i+" J: "+j+" K: "+k);
decoded = new BASE64Decoder().decodeBuffer(encoded);
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte [] decrypted = cipher.doFinal(decoded);
String dec = new String(decrypted);
if(dec.contains("Mary")){
out.write(dec);
out.write("\n");
System.out.println(dec);
break outerloop;
}
mykeyarray[15]++;
}
mykeyarray[14]++;
mykeyarray[15]=0x00;
}
mykeyarray[13]++;
mykeyarray[14]=0x00;
mykeyarray[15]=0x00;
}
out.close();
}
catch(Exception e){
System.out.println("Error: " + e.getMessage());
}
}
}
Your code makes many many mistakes, and I don't know what you are trying to accomplish. So I'll explain why you may receive a BadPaddingException for a CBC cipher:
your key is incorrect
one or both of the last two blocks of ciphertext have been altered
one or more blocks have been removed from the end of the ciphertext
the IV is incorrect and the ciphertext consists of a single block
Good luck hunting down the cause of the exception.
Try to learn more about PKCS#5 padding. It's a special bytes beeing added to plain text before encryption. It can't be correct if the text was decrypted with a wrong key. If you brute-force a key, you will take this error on each key except correct one.
Since decrypting with a random key gives you a random message, you usually don't get correct padding. Just catch the exception and move on.
You will get padding errors approximately 93% of the time when brute forcing a PKCS5 padded message. PKCS5 padding pads out your message with bytes containing the length of the padding. So valid padding is 0x01, 0x2 0x02, 0x03 0x03 0x03, ...., 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF. The odds of correct padding happening in a random message are 1/16 + (1/16)^2 ... (1/16)^16 <.067. Which means you get incorrect padding about 1- %6.7 = 93% of the time.
Someone asked me to decrypt with PHP a string encrypted with the following Java Class.
public class CryptoLibrary {
private Cipher encryptCipher;
private sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
public CryptoLibrary() throws SecurityException{
java.security.Security.addProvider(new com.sun.crypto.provider.SunJCE());
char[] pass = "NNSHHETJKKSNKH".toCharArray();
byte[] salt = {
(byte) 0xa3, (byte) 0x21, (byte) 0x24, (byte) 0x2c,
(byte) 0xf2, (byte) 0xd2, (byte) 0x3e, (byte) 0x19 };
init(pass, salt, iterations);
}
public void init(char[] pass, byte[] salt, int iterations)throws SecurityException{
PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec(salt, 20);
SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey k = kf.generateSecret(new javax.crypto.spec.PBEKeySpec(pass));
encryptCipher = Cipher.getInstance("PBEWithMD5AndDES/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, k, ps);
}
}
public synchronized String encrypt(String str) throws SecurityException{
if(str!=null){
byte[] utf8 = str.getBytes("UTF8");
byte[] enc = encryptCipher.doFinal(utf8);
return encoder.encode(enc);
}
else {
return null;
}
}
}
I don't know any Java so I need some help to understand this encryption.
1) what is the meaning of this line?
PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec(salt,20);
2) what value should I use for the first parameter of
string mcrypt_encrypt ( string $cipher , string $key , string $data , string $mode [, string $iv ] )
3) When should I use MD5 in my php script?
I had to do the same thing for a customer of mine and wrote a few lines of code to help with issue: https://github.com/kevinsandow/PBEWithMD5AndDES
1) It creates the parameters for Password Based Encryption, the salt, which is included in the hash calculations, and the number of iterations that the hash method is executed (on it's own output). It is used to defeat rainbow table attacks, basically an attacker has to go through the same number of iterations to check if the password is correct, and he cannot use a precalculated table because the salt will be different for each password (so you cannot see if somebody has the same password as another user).
2) MCRYPT_DES, and you will need MCRYPT_MODE_CBC for the mode, and PKCS#5 padding of course.
3) Only when you are absolutely sure that its weaknesses are not exposed or when absolutely required for compatibility. Fortunately, it is relatively secure for key derivation functions. Download a pbkdf1 method for PHP and put it in there - if not already included.