How to secure webservice URL? - android

I have an android app with web service urls. If anyone decrypts my apk file, the webservice url will become visible.I am using HTTP POST for calling web service.
Anyone can read the code by decompiling the apk file from this site.
My registration page url got hacked and sending bulk request to this url with post data. I was using a API_KEY and send the API_KEY with post data. API_KEY was stored in gradle.properties file.
I did not used
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'` when its got hacked.
After some search, i know that there is no 100% secure methods to hide url.
My code for registration is :
String link = "http://xxxxxxxxxx.php";
String data = URLEncoder.encode("name", "UTF-8") + "=" + URLEncoder.encode(name, "UTF-8");
data += "&" + URLEncoder.encode("phone", "UTF-8") + "=" + URLEncoder.encode(phone, "UTF-8");
data += "&" + URLEncoder.encode("password", "UTF-8") + "=" + URLEncoder.encode(password, "UTF-8");
URL url = new URL(link);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(data);
wr.flush();
I don't know if it is the correct method to post data to a Url.
How can i secure my source code?
Can i store all my web service url in server?
I am beginner to android. Please help!

I use it this in all android application I developed
gradle.properties
API = http://ec2xxxxx.compute.amazonaws.com
API_KEY = $2c11SoL/NjJ28
create utils.gradle
utils.gradle
class Utils {
static def r = new Random(System.currentTimeMillis())
}
def String toJavaCodeString(String string) {
byte[] b = string.getBytes();
int c = b.length;
StringBuilder sb = new StringBuilder();
sb.append("(new Object() {");
sb.append("int t;");
sb.append("public String toString() {");
sb.append("byte[] buf = new byte[");
sb.append(c);
sb.append("];");
for (int i = 0; i < c; ++i) {
int t = Utils.r.nextInt();
int f = Utils.r.nextInt(24) + 1;
t = (t & ~(0xff << f)) | (b[i] << f);
sb.append("t = ");
sb.append(t);
sb.append(";");
sb.append("buf[");
sb.append(i);
sb.append("] = (byte) (t >>> ");
sb.append(f);
sb.append(");");
}
sb.append("return new String(buf);");
sb.append("}}.toString())");
return sb.toString();}
ext.toJavaCodeString = this.&toJavaCodeString
build.gradle
apply from: "utils.gradle"
android {
defaultConfig {
buildConfigField 'String', 'API', toJavaCodeString(API)
buildConfigField 'String', 'API_KEY', toJavaCodeString(API_KEY)
}}
and access your private url;
public static final String API = BuildConfig.API;

There is no way to protect "secret" information such as URLs embedded in an APK. A determined / motivated hacker can defeat any scheme you care to design ... if he / she has access to a platform where your app is being run.
In order for your app to run, the app running on the user's device needs to be able to decrypt the hidden URL. The user can either intercept the URL in decrypted form in the app's address space, or he / she can reverse engineer the algorithm and decryption key you are using to do the decryption.
Another "attack" is that your app needs to use the URL to make a request. That request can be intercepted on the users device before it is protected by the SSL / TLS channel to your (presumably) HTTPS enabled service.
And on top of that, if you embed a "secret" URL into an app and that secret is compromised and you have to turn off / relocate your server, then you are making problems for all (legitimate, paying, etc) users of your app. They won't be happy campers.
The correct approach is to make your service secure ... and use some kind of authentication mechanism so that hackers need more than just the URL to make requests. Users can / should be issued with individual credentials (e.g. auth keys), and you need to implement a way to invalidate a given users' credentials at the server end.

"URL" stands for Universal Resource Locator. The whole point of a URL is to access some kind of resource, and to do this, you obviously have to tell the network you are connecting to what it is you want to fetch.
Now if you are worried that a hacker can access your source code, then surely he can also hook up Wireshark or Fiddler and simply observe what connections your app is making, and what info you are passing along and receiving back.
Sorry, but I simply can't see any good way around this.
If you're worried about your server/services being hacked or DOS`ed, I think you had better focus on securing them as well as you can, rather than trying to hide them.

I have fixed this in two ways
Encrypted the URL in my code with my private key and on request call i decrypted it again,
public static String encryptIt(String value) {
try {
DESKeySpec keySpec = new DESKeySpec(new byte[]{105, 107, 18, 51, 114, 83, 51, 120, 121});
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(keySpec);
byte[] clearText = value.getBytes("UTF8");
// Cipher is not thread safe
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
// Log.d("aa", "Encrypted: " + value + " -> " + encrypedValue);
return Base64.encodeToString(cipher.doFinal(clearText), Base64.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
and decript it by using this
public static String decryptIt(String value) {
try {
DESKeySpec keySpec = new DESKeySpec(new byte[]{105, 107, 18, 51, 114, 83, 51, 120, 121});//cryptoPass.getBytes("UTF8"));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(keySpec);
byte[] encrypedPwdBytes = Base64.decode(value, Base64.DEFAULT);
// cipher is not thread safe
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypedValueBytes = (cipher.doFinal(encrypedPwdBytes));
// Log.d("aa", "Decrypted: " + value + " -> " + decrypedValue);
return new String(decrypedValueBytes);
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
note in my case that the private key is new byte[]{105, 107, 18, 51, 114, 83, 51, 120, 121} i think it was $ecrEt or something like i forget it.
so if they decompile the APK they wan't be able to find the service link inside you code.
so the base url will be like this public static final String ROOT_API = "aHR0cHSC86LSy9tbS2JpuZW50aWtoYWAbGUJhdC5qbw==";
2- Also you have to add progaurd to your code
BUT, they can smurfing the netweok and find the url if the hacker is advance person, in this case you have to user SSl certificate "https" and make the webserivce POST.
hope you got my point.

Related

AWS Android SDK not able to access s3 in "sub-directory"

I'm using the android sdk generated by AWS API Gateway to get a pre-signed URL for objects in s3 (lambda behind API gateway).
My s3 bucket looks like this:
* module_a
|\
| * file_a
| * subdir_a
| \
| * file_sa
* module_b
This works perfectly for file_a, but for file_sa it doesn't. At least not when I use the android SDK, there I get an URL where the slash is replaced with %25252F.
However, when I test the api in the console, I get the correct URL.
Is there anything I can do with the SDK to fix this?
Update
Here's the chain of code snippets involved in this problem.
Android code to download file (exception happens in last line)
fileName = "css/style.css"; // file in s3
moduleName = "main"; // folder in s3
[...]
ApiClientFactory factory = new ApiClientFactory().credentialsProvider(
aws.credentialsProvider);
apiClient = factory.build(myAPIClient.class);
apiClient.modulesModuleFileGet(fileName.replace("/", "%2F"), moduleName);
URL url = new URL(url_path.getUrl());
URLConnection connection = url.openConnection();
connection.connect();
InputStream in = new BufferedInputStream(connection.getInputStream());
API Gateway
The api endpoint used above is configured with two path parameters (module name and file name). The body mapping template for the call to lambda looks like this:
#set($inputRoot = $input.path('$'))
{
"module" : "$input.params('module')",
"file": "$input.params('file')"
}
Lambda
from __future__ import print_function
import json
import urllib
import boto3
s3 = boto3.client('s3')
def lambda_handler(event, context):
key = event['module'] + "/" + event['file'].replace("%2F", "/")
url = s3.generate_presigned_url(
"get_object",
Params={'Bucket':"mybucket",
'Key': key},
ExpiresIn=60)
return {"url": url}
I've got it to work after following the comments. However I still somehow get double quoted slashes. Here's the working code
Android
public Url modulesModuleFileGet(String fileName, String moduleName) {
try {
String fileNameEnc = URLEncoder.encode(fileName, "UTF-8");
Url ret = getApiClient().modulesModuleFileGet(fileNameEnc, moduleName);
return ret;
} catch (UnsupportedEncodingException e){
Log.e(TAG, "> modulesModuleFileGet(", e);
return null;
}
}
Lambda
def lambda_handler(event, context):
key = event['module'] + "/" + urllib.unquote_plus(urllib.unquote_plus(event['file']))
url = s3.generate_presigned_url(
"get_object",
Params={'Bucket':"my",
'Key': key},
ExpiresIn=60)
return {"url": url}
I'd still welcome further suggestions on how to improve this, but for now it is working. Thanks for the comments pointing me in the right direction.

Should I use URLEncoder for each URL parametr

For each request to server in my android app I need to encode parameters, so my string for URL is looks like
"http://example.com/script.php?param1="+URLEncoder.encode(param1.getText().toString(), "UTF-8")+"param2="+URLEncoder.encode(param2.getText().toString(), "UTF-8")+...."
It works but maybe it is possible to use URLEncoder.encode only one time - like this
URLEncoder.encode("http://example.com/script.php?param1="+param1.getText().toString()+"param2="+param2.getText().toString()+....", "UTF-8")
Would it be ok or there are some cases when it can crash?
URL encoding the whole URL will not work, because it would result in something like
"http%3A%2F%2Fexample.com%2Fscript.php%3Fparam1%3Dasdf%26param2%3Djkl"
i.e. all the special characters in the whole URL would be encoded. You also can not url encode the whole query string, because the = and & characters would be encoded.
You have to encode each parameter value to stop special characters in the parameter interfering with the URL parsing. A helper function may reduce the pain.
String url = "http://example.com/script.php?" + encodeArgs("a", "a + b", "b", "=xxx=");
and something to get you started
public String encodeArgs(String... args) {
final String encoding = "UTF-8";
try {
if (args.length % 2 != 0) {
throw new IllegalArgumentException("number of arguments not even");
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < args.length; i += 2) {
sb.append(URLEncoder.encode(args[i], encoding));
sb.append("=");
sb.append(URLEncoder.encode(args[i + 1], encoding));
sb.append("&");
}
// delete last &, if any
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("unsupported encoding " + encoding, e);
}
}
You should not encode complete URL. Encode only param section or in other words, only parts of it that come from "unreliable sources".
So your 1st attempt "http://example.com/script.php?param1="+URLEncoder.encode(param1.getText().toString(), "UTF-8")+"param2="+URLEncoder.encode(param2.getText().toString(), "UTF-8")+...." is correct, and you should continue with it.
URL encoding in Android and Android: howto parse URL String with spaces to URI object? can be useful for more clarity.

Using Google Cloud Storage JSON api in android

I want to upload image on Google Cloud Storage from my android app. For that I searched and found that GCS JSON Api provides this feature. I did a lot of research for Android sample which demonstrates its use. On the developer site they have provided code example that only support java. I don't know how to use that API in Android. I referred this and this links but couldn't get much idea. Please guide me on how i can use this api with android app.
Ok guys so I solved it and got my images being uploaded in Cloud Storage all good.
This is how:
Note: I used the XML API it is pretty much the same.
First, you will need to download a lot of libraries.
The easiest way to do this is create a maven project and let it download all the dependencies required. From this sample project :
Sample Project
The libraries should be:
Second, you must be familiar with Cloud Storage using the api console
You must create a project, create a bucket, give the bucket permissions, etc.
You can find more details about that here
Third, once you have all those things ready it is time to start coding.
Lets say we want to upload an image:
Cloud storage works with OAuth, that means you must be an authenticated user to use the API. For that the best way is to authorize using Service Accounts. Dont worry about it, the only thing you need to do is in the API console get a service account like this:
We will use this service account on our code.
Fourth, lets write some code, lets say upload an image to cloud storage.
For this code to work you must put your key generated in step 3 in assets folder, i named it "key.p12".
I don't recommend you to do this on your production version, since you will be giving out your key.
try{
httpTransport= new com.google.api.client.http.javanet.NetHttpTransport();
//agarro la key y la convierto en un file
AssetManager am = context.getAssets();
InputStream inputStream = am.open("key.p12"); //you should not put the key in assets in prod version.
//convert key into class File. from inputstream to file. in an aux class.
File file = UserProfileImageUploadHelper.createFileFromInputStream(inputStream,context);
//Google Credentianls
GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(Collections.singleton(STORAGE_SCOPE))
.setServiceAccountPrivateKeyFromP12File(file)
.build();
String URI = "https://storage.googleapis.com/" + BUCKET_NAME+"/"+imagename+".jpg";
HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential);
GenericUrl url = new GenericUrl(URI);
//byte array holds the data, in this case the image i want to upload in bytes.
HttpContent contentsend = new ByteArrayContent("image/jpeg", byteArray );
HttpRequest putRequest = requestFactory.buildPutRequest(url, contentsend);
com.google.api.client.http.HttpResponse response = putRequest.execute();
String content = response.parseAsString();
Log.d("debug", "response is:"+response.getStatusCode());
Log.d("debug", "response content is:"+content);} catch (Exception e) Log.d("debug", "Error in user profile image uploading", e);}
This will upload the image to your cloud bucket.
For more info on the api check this link Cloud XML API
Firstly, You should get the below information by registering your application in the GCP console.
private final String pkcsFile = "xxx.json";//private key file
private final String bucketName = "your_gcp_bucket_name";
private final String projectId = "your_gcp_project_id";
Once you get the credentials, you should put the private key (.p12 or .json) in your assets folder. I'm using JSON format private key file. Also, you should update the image location to upload.
#RequiresApi(api = Build.VERSION_CODES.O)
public void uploadImageFile(String srcFileName, String newName) {
Storage storage = getStorage();
File file = new File(srcFileName);//Your image loaction
byte[] fileContent;
try {
fileContent = Files.readAllBytes(file.toPath());
} catch (IOException e) {
e.printStackTrace();
return;
}
if (fileContent == null || fileContent.length == 0)
return;
BlobInfo.Builder newBuilder = Blob.newBuilder(BucketInfo.of(bucketName), newName);
BlobInfo blobInfo = newBuilder.setContentType("image/png").build();
Blob blob = storage.create(blobInfo, fileContent);
String bucket = blob.getBucket();
String contentType = blob.getContentType();
Log.e("TAG", "Upload File: " + contentType);
Log.e("File ", srcFileName + " uploaded to bucket " + bucket + " as " + newName);
}
private Storage getStorage() {
InputStream credentialsStream;
Credentials credentials;
try {
credentialsStream = mContext.getAssets().open(pkcsFile);
credentials = GoogleCredentials.fromStream(credentialsStream);
} catch (IOException e) {
e.printStackTrace();
return null;
}
return StorageOptions.newBuilder()
.setProjectId(projectId).setCredentials(credentials)
.build().getService();
}

Encryption & Decryption algorithm

I am working on mobile product. We are using the data in xml document. In order to keep our data secure we need an encryption algorithm(but we don't want the existing algorithm to import)
Can u give me some steps to encrypt the data.(if code example is most welcome).
To be more secure, you have to do with your own secret key. Try to use this code
KeyStore ks = KeyStore.getInstance();
// get the names of all keys created by our app
String[] keyNames = ks.saw("");
// store a symmetric key in the keystore
SecretKey key = Crypto.generateKey();
boolean success = ks.put("secretKey1", key.getEncoded());
// check if operation succeeded and get error code if not
if (!success) {
int errorCode = ks.getLastError();
throw new RuntimeException("Keystore error: " + errorCode);
}
// get a key from the keystore
byte[] keyBytes = ks.get("secretKey1");
SecretKey key = new SecretKeySpec(keyBytes, "AES");
// delete a key
boolean success = ks.delete("secretKey1");
If you want to develop your own encryption scheme, be prepared to embark on a research project. You can use any of standard encryption algorithms like AES/DES etc, with your private keys that are sufficiently long and difficult to crack.
public string PassEncrypt(string Password)
{
// Encrypting the password entered by User
// ======================================================
MD5 md5 = new MD5CryptoServiceProvider();
md5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(Password));
byte[] result = md5.Hash;
StringBuilder strBuilder = new StringBuilder();
for (int i = 0; i < result.Length; i++)
{
strBuilder.Append(result[i].ToString("x2"));
}
return strBuilder.ToString();
// ======================================================
}
OR
You may refer on this links :
developer.motorala.com
codereview.stackexchange.com
android snippets
java-tips.org

Authorization request header OAuth 2.0

I am trying to implement OAuth 2.0 on a project I am working on. I am able to authenicate with the authenication server however I am having problems with the resource server. We are using a MAC token(spec). You can see in 3.1 of the spec that I need to send a Authorization request header which includes the following. I can't make any sense of that. Can somebody show me what I am suppose to do here?
credentials = "MAC" [ RWS 1#param ]
param = id /
nonce /
body-hash /
ext /
mac
id = "id" "=" <"> plain-string <">
nonce = "nonce" "=" <"> 1*DIGIT ":" plain-string <">
body-hash = "bodyhash" "=" <"> plain-string <">
ext = "ext" "=" <"> plain-string <">
mac = "mac" "=" <"> plain-string <">
plain-string = 1*( %x20-21 / %x23-5B / %x5D-7E )
UPDATE
I feel I am getting somewhere but feel like I am still so far from solving this problem.
So I am building something like the following
StringBuilder header = new StringBuilder("MAC ").append("id=\"").append(sharedPrefs.getString(Constants.ACCESS_TOKEN, "error")).append("\",nonce=\"").append(createNonce()).
append("\",bodyhash=\"").append(bodyHash).append("\",mac=\"").append(mac).append("\"");
I calculate the body hash like so
public static String SHA256(String text) throws UnsupportedEncodingException {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] shahash = new byte[40];
md.update(text.getBytes("iso-8859-1"), 0, text.length());
shahash = md.digest();
return Base64.encodeToString(shahash, Base64.DEFAULT);
}
And the mac like this
private String hmacSHA256(String data) throws Exception {
String key = sharedPrefs.getString(Constants.SECRET, "error");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
byte[] hmacData = mac.doFinal(data.getBytes("UTF-8"));
//Log.i(TAG, "BYTE ARRAY TO STRING: " + Base64.encodeToString(hmacData, Base64.DEFAULT));
String value = Base64.encodeToString(hmacData, Base64.DEFAULT);
return value;
}
I am having problems as the server just doesn't respond. This is really driving me crazy, I can't image that this documentation is clear to anybody.
you need to be aware that the MAC access authentication scheme is an extension of the oauth2 protocol, similar to the HTTP Basic access, but optional, so there is no need to implement this in every resource server.
So if you make an unauthenticated request and the resource server respond something like:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: MAC
The MAC, means that you are allowed to use this scheme of authentication.
The most common authentication scheme implemented nowadays for oauth2-based resource servers is: bearer.
If you still have concerns about how to implement this spec, there is an excelent source for Android on Github to accomplish it. And probably the java class you need is this.
Have fun!

Categories

Resources