title: Bad Base64 input character at 0: 60(decimal) crash after in-app billing
body: <p>I've been following the <a href="http://www.techotopia.com/index.php/Integrating_Google_Play_In-app_Billing_into_an_Android_Application_%E2%80%93_A_Tutorial" rel="nofollow">Techotopia in-app billing tutorial</a>
  and keep getting my release versions crashing with the following errors</p>
  
  <pre><code>java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=10001, result=-1, data=Intent { (has extras) }} to activity {com.spudproducts.inappbilling/com.spudproducts.inappbilling.InAppBillingActivity}: java.lang.IllegalArgumentException: com.spudproducts.inappbilling.util.Base64DecoderException: Bad Base64 input character at 0: 60(decimal)
  at android.app.ActivityThread.deliverResults(ActivityThread.java:3556)
  at android.app.ActivityThread.handleSendResult(ActivityThread.java:3599)
  at android.app.ActivityThread.access$1300(ActivityThread.java:148)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1332)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:135)
  at android.app.ActivityThread.main(ActivityThread.java:5274)
  at java.lang.reflect.Method.invoke(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
  Caused by: java.lang.IllegalArgumentException: com.spudproducts.inappbilling.util.Base64DecoderException: Bad Base64 input character at 0: 60(decimal)
  at com.spudproducts.inappbilling.util.Security.generatePublicKey(Security.java:106)
  at com.spudproducts.inappbilling.util.Security.verifyPurchase(Security.java:77)
  at com.spudproducts.inappbilling.util.IabHelper.handleActivityResult(IabHelper.java:479)
  at com.spudproducts.inappbilling.InAppBillingActivity.onActivityResult(InAppBillingActivity.java:72)
  at android.app.Activity.dispatchActivityResult(Activity.java:6179)
  at android.app.ActivityThread.deliverResults(ActivityThread.java:3552)
  ... 10 more
  Caused by: com.spudproducts.inappbilling.util.Base64DecoderException: Bad Base64 input character at 0: 60(decimal)
  at com.spudproducts.inappbilling.util.Base64.decode(Base64.java:546)
  at com.spudproducts.inappbilling.util.Base64.decode(Base64.java:474)
  at com.spudproducts.inappbilling.util.Base64.decode(Base64.java:420)
  at com.spudproducts.inappbilling.util.Security.generatePublicKey(Security.java:96)    
  </code></pre>
  
  <p>The app ran fine in debug mode previously (using android.test.purchased), but now is crashing despite me reverting any changes, with the error log </p>
  
  <pre><code>02-16 16:09:22.832  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Starting async operation: launchPurchaseFlow
  02-16 16:09:22.833  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Constructing buy intent for android.test.purchased, item type: inapp
  02-16 16:09:22.906  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Launching buy intent for android.test.purchased. Request code: 10001
  02-16 16:09:28.687  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Ending async operation: launchPurchaseFlow
  02-16 16:09:28.688  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Successful resultcode from purchase activity.
  02-16 16:09:28.688  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Purchase data: {"packageName":"com.spudproducts.payforatap","orderId":"transactionId.android.test.purchased","productId":"android.test.purchased","developerPayload":"mypurchasetoken","purchaseTime":0,"purchaseState":0,"purchaseToken":"inapp:com.spudproducts.payforatap:android.test.purchased"}
  02-16 16:09:28.688  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Data signature:
  02-16 16:09:28.688  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Extras: Bundle[{INAPP_PURCHASE_DATA={"packageName":"com.spudproducts.payforatap","orderId":"transactionId.android.test.purchased","productId":"android.test.purchased","developerPayload":"mypurchasetoken","purchaseTime":0,"purchaseState":0,"purchaseToken":"inapp:com.spudproducts.payforatap:android.test.purchased"}, INAPP_DATA_SIGNATURE=, RESPONSE_CODE=0}]
  02-16 16:09:28.688  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Expected item type: inapp
  02-16 16:09:28.694  22976-22976/com.spudproducts.payforatap E/IABUtil/Security﹕ Purchase verification failed: missing data.
  02-16 16:09:28.694  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Purchase signature successfully verified.
  02-16 16:09:28.695  22976-22976/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Starting async operation: refresh inventory
  02-16 16:09:28.702  22976-23140/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Querying owned items, item type: inapp
  02-16 16:09:28.702  22976-23140/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Package name: com.spudproducts.payforatap
  02-16 16:09:28.702  22976-23140/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Calling getPurchases with continuation token: null
  02-16 16:09:28.749  22976-23140/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Owned items response: 0
  02-16 16:09:28.749  22976-23140/com.spudproducts.payforatap E/IABUtil/Security﹕ Purchase verification failed: missing data.
  02-16 16:09:28.749  22976-23140/com.spudproducts.payforatap D/com.spudproducts.inappbilling﹕ Sku is owned: android.test.purchased
  02-16 16:09:28.756  22976-22976/com.spudproducts.payforatap I/Timeline﹕ Timeline: Activity_idle id: android.os.BinderProxy@ff8bddb time:63182857
  02-16 16:09:28.756  22976-23140/com.spudproducts.payforatap E/IABUtil/Security﹕ Base64 decoding failed.
  02-16 16:09:28.763  22976-23140/com.spudproducts.payforatap E/AndroidRuntime﹕ FATAL EXCEPTION: Thread-4554
      Process: com.spudproducts.payforatap, PID: 22976
      java.lang.IllegalArgumentException: com.spudproducts.inappbilling.util.Base64DecoderException: Bad Base64 input character at 0: 60(decimal)
  </code></pre>
  
  <p>Here is the code I have used, straight out of the tutorial, I've spent days on this and even re-did the entire project in an effort to fix it, but am facing the same problem</p>
  
  <p>InAppBillingActivity.java</p>
  
  <pre><code>import android.app.Activity;
  import android.content.Intent;
  import android.support.v7.app.ActionBarActivity;
  import android.os.Bundle;
  import android.util.Log;
  import android.view.Menu;
  import android.view.MenuItem;
  import android.view.View;
  import android.widget.Button;
  
  import com.spudproducts.inappbilling.util.IabHelper;
  import com.spudproducts.inappbilling.util.IabResult;
  import com.spudproducts.inappbilling.util.Inventory;
  import com.spudproducts.inappbilling.util.Purchase;
  
  
  public class InAppBillingActivity extends Activity {
  
  
      private static final String TAG =
              "com.spudproducts.inappbilling";
      IabHelper mHelper;
      static final String ITEM_SKU = "android.test.purchased";
      //static final String ITEM_SKU = "com.example.buttonclick";
  
      private Button clickButton;
      private Button buyButton;
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_in_app_billing);
  
          buyButton = (Button)findViewById(R.id.buyButton);
          clickButton = (Button)findViewById(R.id.clickButton);
          clickButton.setEnabled(false);
  
          String base64EncodedPublicKey =
                  "&lt;keyWhichWasCheckedABillionTimes&gt;";
  
          mHelper = new IabHelper(this, base64EncodedPublicKey);
  
          mHelper.startSetup(new
                                     IabHelper.OnIabSetupFinishedListener() {
                                         public void onIabSetupFinished(IabResult result)
                                         {
                                             if (!result.isSuccess()) {
                                                 Log.d(TAG, "In-app Billing setup failed: " +
                                                         result);
                                             } else {
                                                 Log.d(TAG, "In-app Billing is set up OK");
                                                 mHelper.enableDebugLogging(true, TAG);
                                             }
                                         }
                                     });
      }
  
  
      public void buttonClicked (View view)
      {
          clickButton.setEnabled(false);
          buyButton.setEnabled(true);
      }
  
      public void buyClick(View view) {
          mHelper.launchPurchaseFlow(this, ITEM_SKU, 10001,
                  mPurchaseFinishedListener, "mypurchasetoken");
      }
  
      @Override
      protected void onActivityResult(int requestCode, int resultCode,
                                      Intent data)
      {
          if (!mHelper.handleActivityResult(requestCode,
                  resultCode, data)) {
              super.onActivityResult(requestCode, resultCode, data);
          }
      }
  
      IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
              = new IabHelper.OnIabPurchaseFinishedListener() {
          public void onIabPurchaseFinished(IabResult result,
                                            Purchase purchase)
          {
              if (result.isFailure()) {
                  // Handle error
                  return;
              }
              else if (purchase.getSku().equals(ITEM_SKU)) {
                  consumeItem();
                  buyButton.setEnabled(false);
              }
  
          }
      };
  
  
      public void consumeItem() {
          mHelper.queryInventoryAsync(mReceivedInventoryListener);
      }
  
      IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener
              = new IabHelper.QueryInventoryFinishedListener() {
          public void onQueryInventoryFinished(IabResult result,
                                               Inventory inventory) {
  
              if (result.isFailure()) {
                  // Handle failure
              } else {
                  mHelper.consumeAsync(inventory.getPurchase(ITEM_SKU),
                          mConsumeFinishedListener);
              }
          }
      };
  
      IabHelper.OnConsumeFinishedListener mConsumeFinishedListener =
              new IabHelper.OnConsumeFinishedListener() {
                  public void onConsumeFinished(Purchase purchase,
                                                IabResult result) {
  
                      if (result.isSuccess()) {
                          clickButton.setEnabled(true);
                      } else {
                          // handle error
                      }
                  }
              };
  
      @Override
      public void onDestroy() {
          super.onDestroy();
          if (mHelper != null) mHelper.dispose();
          mHelper = null;
      }
  
  }
  </code></pre>
  
  <p>And the modified verifyPurchase from Security.java as fixed in the tutorial</p>
  
  <pre><code>public static boolean verifyPurchase(String base64PublicKey,
                                           String signedData, String signature) {
          if (TextUtils.isEmpty(signedData) ||
                  TextUtils.isEmpty(base64PublicKey) ||
                  TextUtils.isEmpty(signature)) {
              Log.e(TAG, "Purchase verification failed: missing data.");
              if (BuildConfig.DEBUG) {
                  return true;
              }
              return false;
          }
  
          PublicKey key = Security.generatePublicKey(base64PublicKey);
          return Security.verify(key, signedData, signature);
      }
  </code></pre>
  
  <p>I've tried searching everywhere for a fix, and tried to amend heaps of the code to no avail (before going back to the tutorial code)</p>
  
  <p>Any help would be much appreciated :)</p>