I'm having a real nightmare trying to get the PesaPal API to work for me using Ruby...
I appreciate that it's probably not the most commonly used API but if there's anybody online here who has more experience using OAuth, and/or PHP experience who could offer a fresh pair of eyes I'd appreciate it.
So the PesaPal developer site is here: http://developer.pesapal.com
Their API docs don't give away too many clues about how to use OAuth with their site and I don't understand PHP well enough to be sure I've read their PHP sample correctly.
Here's my attempt at implementing this in Ruby:
require 'oauth'
require 'uri'
key = '<my sandbox key>'
sec = '<my sandbox secret>'
API_DOMAIN = 'https://demo.pesapal.com'
# An XML string of param data to include with our request
RAW_XML = %{<?xml version=\"1.0\" encoding=\"utf-8\"?><PesapalDirectOrderInfo xmlns:xsi=\"http://www.w3.org/2001/XMLSchemainstance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" Amount=\"12.34\" Description=\"Bob Test 1\" Type=\"MERCHANT\" Reference=\"808\" FirstName=\"Bo\" LastName=\"Tester\" Email=\"bodacious#bodacious.com\" xmlns=\"http://www.pesapal.com\" />}
# Escape the XML
#post_xml = URI.escape(RAW_XML)
# Create a new OAuth Consumer
#consumer = OAuth::Consumer.new(key, sec, {
site: API_DOMAIN,
scheme: :query_string
})
# The signed request object
#signed_request = #consumer.create_signed_request('get', "#{API_DOMAIN}/API/PostPesapalDirectOrderV4")
# Join the pesapal_request_data and oauth_callback with '&' for valid URL params
#params = {
oauth_callback: URI.escape('http://localhost:3000'),
pesapal_request_data: #post_xml,
}.map { |k,v| "#{k}=#{v}" }.join('&')
# This is the URL we should redirect to
puts redirect_url = "#{#signed_request.path}&#{#params}"
When I try to visit the URL returned by this code, I get: Problem: signature_invalid | Advice: > | back from the API.
Can anyone think of what I'm doing wrong here?
Thanks
Check out the pesapal RubyGem here ... https://rubygems.org/gems/pesapal ... it handles all that stuff for you.
I had the same problem and I solved it by creating a method to manually sign the url. create your own method to get the oauth_nonce like this.
def nonce
Array.new( 5 ) { rand(256) }.pack('C*').unpack('H*').first
end
The signature method looks like this:
def signature
key = percent_encode( #consumer_secret ) + '&' + percent_encode( #token_secret )
digest = OpenSSL::Digest::Digest.new( 'sha1' )
hmac = OpenSSL::HMAC.digest( digest, key, #base_str )
Base64.encode64( hmac ).chomp.gsub( /\n/, '' )
end
This is the #base_str instance variable:
#base_str = [#req_method,
percent_encode( req_url ),
percent_encode( query_string )
].join( '&' )
This is the query_string method:
def query_string
pairs = []
#params.sort.each { | key, val |
pairs.push( "#{ percent_encode( key ) }=#{ percent_encode( val.to_s ) }" )
}
pairs.join '&'
end
You can then use the signature method above to get the oauth_signature param to use in the resulting url
I know the OP was interested in a solution that addresses the Ruby domain, but I feel it may be helpful to note that I was getting a similar problem on PHP and the solution was to ensure I had no white space in the XML post data structure.
WRONG:
<?xml version="1.0" encoding="utf-8"?>
<PesapalDirectOrderInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
Amount="200"
Currency="KES"
Description="Computer Accessory"
Type="MERCHANT"
Reference="dd19f9ede4db6f0a13b7053111f02825"
FirstName="Stack"
LastName="Overflow"
Email="so#stackoverflow.com"
PhoneNumber=""
xmlns="http://www.pesapal.com" >
<lineitems>
<lineitem uniqueid="29"
particulars="Keyboard"
quantity="1"
unitcost="200"
subtotal="200.00" >
</lineitem>
</lineitems>
</PesapalDirectOrderInfo>
CORRECT:
<?xml version="1.0" encoding="utf-8"?><PesapalDirectOrderInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Amount="200" Currency="KES" Description="Computer Accessory" Type="MERCHANT" Reference="dd19f9ede4db6f0a13b7053111f02825" FirstName="Stack" LastName="Overflow" Email="so#stackoverflow.com" PhoneNumber="" xmlns="http://www.pesapal.com" ><lineitems><lineitem uniqueid="29" particulars="Keyboard" quantity="1" unitcost="200" subtotal="200.00" ></lineitem></lineitems></PesapalDirectOrderInfo>
Related
I have been working on a way to implement HMAC verification in python with flask for the selly.gg merchant website.
So selly's dev documentation give these following examples to verify HMAC signatures (in PHP and ruby): https://developer.selly.gg/?php#signing-validating
(code below:)
PHP:
<?php
$signature = hash_hmac('sha512', json_encode($_POST), $secret);
if hash_equals($signature, $signatureFromHeader) {
// Webhook is valid
}
?>
RUBY:
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha512'), secret, payload.to_json)
is_valid_signature = ActiveSupport::SecurityUtils.secure_compare(request.headers['X-Selly-Signature'], signature)
So, so far what I could figure out: They don't encode with base64 (like shopify and others do), it uses SHA-512, it encodes the secret code alongside json response data and finally the request header is 'X-Selly-Signature'
I've made the following code so far (based on shopify's code for HMAC signing https://help.shopify.com/en/api/getting-started/webhooks):
SECRET = "secretkeyhere"
def verify_webhook(data, hmac_header):
digest = hmac.new(bytes(SECRET, 'ascii'), bytes(json.dumps(data), 'utf8'), hashlib.sha512).hexdigest()
return hmac.compare_digest(digest, hmac_header)
try:
responsebody = request.json #line:22
status = responsebody['status']#line:25
except Exception as e:
print(e)
return not_found()
print("X Selly sign: " + request.headers.get('X-Selly-Signature'))
verified = verify_webhook(responsebody, request.headers.get('X-Selly-Signature'))
print(verified)
However selly has a webhook simulator, and even with the proper secret key and valid requests, the verify_webhook will always return False. I tried contacting Selly support, but they couldn't help me more than that
You can test the webhook simulator at the following address:
https://selly.io/dashboard/{your account}/developer/webhook/simulate
You're nearly right except that you don't need to json.dumps the request data. This will likely introduce changes into output, such as changes to formatting, that won't match the original data meaning the HMAC will fail.
E.g.
{"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"}
is different to:
{
"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"
}
which is actually:
{x0ax20x20"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"x0a}
A hash will be completely different for the two inputs.
See how json.loads and json.dumps will modify the formatting and therefore the hash:
http_data = b'''{
"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"
}
'''
print(http_data)
h = hashlib.sha512(http_data).hexdigest()
print(h)
py_dict = json.loads(http_data) # deserialise to Python dict
py_str = json.dumps(py_dict) # serialise to a Python str
py_bytes = json.dumps(py_dict).encode('utf-8') # encode to UTF-8 bytes
print(py_str)
h2 = hashlib.sha512(py_bytes).hexdigest()
print(h2)
Output:
b'{\n "id":"fd87d909-fbfc-466c-964a-5478d5bc066a"\n}\n'
364325098....
{"id": "fd87d909-fbfc-466c-964a-5478d5bc066a"}
9664f687a....
It doesn't help that Selly's PHP example shows something similar. In fact, the Selly PHP example is useless as the data won't be form encoded anyway, so the data won't be in $_POST!
Here's my little Flask example:
import hmac
import hashlib
from flask import Flask, request, Response
app = Flask(__name__)
php_hash = "01e5335ed340ef3f211903f6c8b0e4ae34c585664da51066137a2a8aa02c2b90ca13da28622aa3948b9734eff65b13a099dd69f49203bc2d7ae60ebee9f5d858"
secret = "1234ABC".encode("ascii") # returns a byte object
#app.route("/", methods=['POST', 'GET'])
def selly():
request_data = request.data # returns a byte object
hm = hmac.new(secret, request_data, hashlib.sha512)
sig = hm.hexdigest()
resp = f"""req: {request_data}
sig: {sig}
match: {sig==php_hash}"""
return Response(resp, mimetype='text/plain')
app.run(debug=True)
Note the use of request.data to get the raw byte input and the simple use of encode on the secret str to get the encoded bytes (instead of using the verbose bytes() instantiation).
This can be tested with:
curl -X "POST" "http://localhost:5000/" \
-H 'Content-Type: text/plain; charset=utf-8' \
-d "{\"id\":\"fd87d909-fbfc-466c-964a-5478d5bc066a\"}"
I also created a bit of PHP to validate both languages create the same result:
<?php
header('Content-Type: text/plain');
$post = file_get_contents('php://input');
print $post;
$signature = hash_hmac('sha512', $post, "1234ABC");
print $signature;
?>
I'd like to use some web service via its API. In documentation I found an example request written with PHP SoapClient. But I am using RoR and I have no PHP experience. Could someone tell me how should I write the same in RoR, or at least translate it to plain HTTP terminology?
<?php
$soap = new SoapClient(“https://secure.przelewy24.pl/external/wsdl/service.php?wsdl”);
$test = $soap->TestAccess(“9999”, “anuniquekeyretrievedfromprzelewy24”);
if ($test)
echo ‘Access granted’;
else
echo ‘Access denied’;
?>
Edit: particularly I'd like to know what should I do with TestAccess method, because there's no methods in plain HTTP. Should I join this name with URL?
To make your life easier, check out a gem that allows you to simplify SOAP access, like savon.
Then the code could be translated as
# create a client for the service
client = Savon.client(wsdl: 'https://secure.przelewy24.pl/external/wsdl/service.php?wsdl')
This will automatically parse the possible methods to client that are offered in the SOAP API (defined in the WSDL). To list the possible operations, type
client.operations
In your case this will list
[:test_access, :trn_refund, :trn_by_session_id, :trn_full_by_session_id, :trn_list_by_date, :trn_list_by_batch, :trn_full_by_batch, :payment_methods, :currency_exchange, :refund_by_id, :trn_register, :trn_internal_register, :check_nip, :company_register, :company_update, :batch_list, :trn_dispatch, :charge_back, :trn_check_funds, :check_merchant_funds, :transfer_merchant_funds, :verify_transaction, :register_transaction, :deny_transaction, :batch_details]
Then to call the method, do the following
response = client.call(:test_access, message: { test_access_in: 9999 })
response = client.call(:test_access, message: {
test_access_in: 9999 }
test_access_out: "anuniquekeyretrievedfromprzelewy24"
)
response.body
=> {:test_access_response=>{:return=>false}}
this gets a result, but I have no idea what it means.
I've included an entire controller method that we use in production as an example but essentially you want to pass in your xml/wsdl request as the body of the HTTP request and then parse the response as xml, or what we used below which is rexml for easier traversing of the returned doc.
def get_chrome_styles
require 'net/http'
require 'net/https'
require 'rexml/document'
require 'rexml/formatters/pretty'
xml = '<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:description7b.services.chrome.com">
<soapenv:Header/>
<soapenv:Body>
<urn:StylesRequest modelId="' + params[:model_id] + '">
<urn:accountInfo number="[redacted]" secret="[redacted]" country="US" language="en" behalfOf="trace"/>
<!--Optional:-->
</urn:StylesRequest>
</soapenv:Body>
</soapenv:Envelope>'
base_url = 'http://services.chromedata.com/Description/7b?wsdl'
uri = URI.parse( base_url )
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new("/Description/7b?wsdl")
request.add_field('Content-Type', 'text/xml; charset=utf-8')
request.body = xml
response = http.request( request )
doc = REXML::Document.new(response.body)
options = []
doc.get_elements('//style').each do |division|
puts division
options << { :id => division.attributes['id'], :val => division.text }
end
respond_to do |format|
format.json { render :json => options.to_json }
end
end
Since one week I'm trying to retrieve some datas from a Dolibarr application including Dolibarr's webservices.
In few words, I'm trying to make a soap request to retrieve user's informations.
At first, I tried to instantiate SoapClient with 'wsdl' and 'trace' parameters, in vain.. SoapClient's object was never been create !
Secondly, I made a made a classical SoapClient's object without 'wsdl', however I used : 'location', 'action', 'namespace', 'soap_ns', 'trace'; It was a success (I think) but It did't work when I called Client's call method.. My dolibarrkey did't match my key on the webservice, but their keys are the same (copy & paste).
For more explanations take a look to dolibarr api (to retrieve datas) with the xlm dataformat.
Link to getUser web service (click on getUser to show parameters):
http://barrdoli.yhapps.com/webservices/server_user.php
Link to xml dataformat (for the SOAP request maybe):
http://barrdoli.yhapps.com/webservices/server_user.php?wsdl
from pysimplesoap.client import SoapClient, SoapFault
import sys
def listThirdParties():
# create a simple consumer
try:
# client = SoapClient(
# "[MyAppDomain]/webservices/server_user.php")
# print(client)
# client = SoapClient(wsdl="[MyAppDomain]/webservices/server_user.php?wsdl", trace=True)
client = SoapClient(
location = "[myAppDomain]/webservices/server_user.php",
action = '[myAppDomain]/webservices/server_user.php?wsdl', # SOAPAction
namespace = "[myAppDomain]/webservices/server_user.php",
soap_ns='soap',
trace = True,
)
print("connected bitch")
except:
print("error connect")
message = dict()
message['use'] = "encoded"
message["namespace"] = "http://www.dolibarr.org/ns/"
message["encodingStyle"] = "http://schemas.xmlsoap.org/soap/encoding/"
message["message"] = "getUserRequest"
parts = dict()
auth = dict()
auth['dolibarrkey'] = '********************************'
auth['sourceapplication'] = 'WebServicesDolibarrUser'
auth['login'] = '********'
auth['password'] = '********'
auth['entity'] = ''
parts["authentication"] = auth
parts["id"] = 1
parts["ref"] = "ref"
parts["ref_ext"] = "ref_ext"
message["parts"] = parts
# call the remote method
response = client.call(method='getUser', kwargs=message)
# extract and convert the returned value
# result = response.getUser
# return int(result)
print(response)
pass
I've "BAD_VALUE_FOR_SECURITY_KEY" into a xlm response, I think it's my request which made with a bad xml dataformat..
shell response :
-------- RESPONSE -------
b'<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://www.dolibarr.org/ns/"><SOAP-ENV:Body><ns1:getUserResponse xmlns:ns1="http://barrdoli.yhapps.com/webservices/server_user.php"><result xsi:type="tns:result"><result_code xsi:type="xsd:string">BAD_VALUE_FOR_SECURITY_KEY</result_code><result_label xsi:type="xsd:string">Value provided into dolibarrkey entry field does not match security key defined in Webservice module setup</result_label></result><user xsi:nil="true" xsi:type="tns:user"/></ns1:getUserResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>'
I really want to know how should I do to make a working soap request with a clean xlm dataformat.
Thanks
Did you try to setup WebService Security (WSSE) with client object? Following examples are taken from https://code.google.com/p/pysimplesoap/wiki/SoapClient
client['wsse:Security'] = {
'wsse:UsernameToken': {
'wsse:Username': 'testwservice',
'wsse:Password': 'testwservicepsw',
}
}
or try to Setup AuthHeaderElement authentication header
client['AuthHeaderElement'] = {'username': 'mariano', 'password': 'clave'}
I'm trying to work with the examples on the Twitter dev site but can't seem to get to the same signature as they have.
I am trying to complete step 3 on https://dev.twitter.com/docs/auth/implementing-sign-twitter because I am getting an error "Invalid or expired token" but I know it isn't because I've only just been given it, so it must be something wrong with my data packet.
The code I am using to try and generate this is:
// testing bit
$oauth = array(
'oauth_consumer_key'=>'cChZNFj6T5R0TigYB9yd1w',
'oauth_nonce'=>'a9900fe68e2573b27a37f10fbad6a755',
'oauth_signature_method'=>'HMAC-SHA1',
'oauth_timestamp'=>'1318467427',
'oauth_token'=>'NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0',
'oauth_version'=>'1.0'
);
$this->o_secret = 'LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE';
$this->c_secret = 'kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw';
ksort($oauth);
$string = rawurlencode(http_build_query($oauth));
$new_string = strtoupper($http_method).'&'.rawurlencode($main_url[0]).'&'.$string;
// The request_token request doesn't need a o_secret because it doesn't have one!
$sign_key = strstr($fullurl,'request_token') ? $this->c_secret.'&' : $this->c_secret.'&'.$this->o_secret;
echo urlencode(base64_encode(hash_hmac('sha1',$new_string,$sign_key,true)));exit;
And I'm assuming that the keys listed on this page are in fact correct: https://dev.twitter.com/docs/auth/creating-signature. So in that case the signature should be 39cipBtIOHEEnybAR4sATQTpl2I%3D.
If you can spot what I'm missing that would be great.
Your consumer secret and token secret are incorrect for the page you reference. If you look further up the page you can see that they should be:
Consumer secret: L8qq9PZyRg6ieKGEKhZolGC0vJWLw8iEJ88DRdyOg
Token secret: veNRnAWe6inFuo8o2u8SLLZLjolYDmDP7SzL0YfYI
Also in Step 3 you need to include the oauth_verifier in the list of parameters when calculating your signature base string.
I'm not familiar with PHP so I haven't checked your code to calculate the signature.
This code has now worked - I will tidy it up from there :)
// This function is to help work out step 3 in the process and why it is failing
public function testSignature(){
// testing bit
$oauth = array(
'oauth_consumer_key'=>'cChZNFj6T5R0TigYB9yd1w',
'oauth_nonce'=>'a9900fe68e2573b27a37f10fbad6a755',
'oauth_signature_method'=>'HMAC-SHA1',
'oauth_timestamp'=>'1318467427',
'oauth_token'=>'NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0',
'oauth_version'=>'1.0'
);
$this->o_secret = 'LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE';
$this->c_secret = 'kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw';
ksort($oauth);
$string = http_build_query($oauth);
$new_string = strtoupper($http_method).'&'.$main_url[0].'&'.$string;
$new_string = 'POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521';
// The request_token request doesn't need a o_secret because it doesn't have one!
$sign_key = $this->c_secret.'&'.$this->o_secret;
echo 'Should be: tnnArxj06cWHq44gCs1OSKk/jLY=<br>';
echo 'We get: '.base64_encode(hash_hmac('sha1',$new_string,$sign_key,true));
exit;
}
you want to access token from twitter and sign in implementation you can see in this example.
1) http://www.codexworld.com/login-with-twitter-using-php/
and this one for timeline tweets
2) http://www.codexworld.com/create-custom-twitter-widget-using-php/
may be this help you .
I am getting an error while trying to send a soap request (soapCall) to the server.
Fatal error: Uncaught SoapFault exception: [ns1:InvalidSecurity] An error was discovered processing the <wsse:Security> header
I need to send the ws-security header
<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-1" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>userID</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">passwd</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">ZTQ3YmJjZmM1ZTU5ODg3YQ==</wsse:Nonce>
<wsu:Created>2013-07-05T19:55:36.458Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
After a lot of research I think the issue I got is the nonce didnt meet the requirement. As I am making up the soap header looks like the example I got. The only unknown element is to calculating this nonce...
From the example nonce I got, its a set of 24 numbers + alphabet + special character
Something like this
ZTQ3YmJjZmM1ZTU5ODg3YQ==
But however, I am not too sure how do you calculate the wsse nonce from php...is there any standard?
the code I had
$nonce = sha1(mt_rand());
Result
dabddf9dbd95b490ace429f7ad6b55c3418cdd58
which is something completely different than the example...and I believe this is the reason why this code is not working.
So I am doing more research and now I am using this
$NASC = substr(md5(uniqid('the_password_i_am _using', true)), 0, 16);
$nonce = base64_encode($NASC);
Result
NzJlMDQ4OTAyZWIxYWU5ZA==
Now, it looks similar to the example but I still getting that error showed from the beginning.
Can someone give me a hand please?
some further testing with soapUI.
same userID and passwd, set the passwordtype to passwordtext
and it is working.
is anyone know how do the soapUI calculate the nonce? or have any idea how soapUI passing the ws-security?
try something like this
string usn = "MyUsername";
string pwd = "MyPassword";
DateTime created = DateTime.Now.ToUniversalTime();
var nonce = getNonce();
string nonceToSend = Convert.ToBase64String(Encoding.UTF8.GetBytes(nonce));
string createdStr = created.ToString("yyyy-MM-ddTHH:mm:ssZ");
string passwordToSend = GetSHA1String(nonce + createdStr + pwd);
and functions:
protected string getNonce()
{
string phrase = Guid.NewGuid().ToString();
return phrase;
}
protected string GetSHA1String(string phrase)
{
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
string test = Convert.ToString(hashedDataBytes);
return Convert.ToBase64String(hashedDataBytes);
}
As uniqid() is based on a Pseudo-Random Number Generator, it does not provide enough entropy. Siehe Insufficient Entropy For Random Values
$nonce = base64_encode( bin2hex( openssl_random_pseudo_bytes( 16 ) ) );
If you don't have the OpenSSL module try this fallback to mcrypt_create_iv() see:
https://github.com/padraic/SecurityMultiTool/blob/master/library/SecurityMultiTool/Random/Generator.php
Microsoft defines the WS-Security nonce as:
The nonce is 16 bytes long and is passed along as a base64 encoded value.
The following PHP code generates a code that follows the Microsoft .Net WS-Security Standard:
$prefix = gethostname();
$nonce = base64_encode( substr( md5( uniqid( $prefix.'_', true)), 0, 16));
Some testing with no $prefix was successful, but the production version of this code uses the $prefix with no authentication problems encountered so far. The original version of this nonce code came from the following library (with a modification to the number of characters to return in substr):
http://code.ronoaldo.net/openemm/src/e25a2bad5aa7/webservices/WSSESoapClient.php#cl-267