OiMae Blog

OiMae Monkey Creators

Why the future behind passwords could be SSL

I was reading an article about password today from Jeff Atwood (http://www.codinghorror.com/blog/2011/09/cutting-the-gordian-knot-of-web-identity.html) and he was talking about how passwords are a “disease” on the Internet. He proposed a solution which stores passwords in a central safe and stuff. However, whats the point of creating a random password or even using a username and password? Why cant a x509 certificate identify and authenticate you?

I was thinking of this process and wanted to know what everyone would think of this. If everyone thinks that this would work or is a good idea, please tell tell me what you think, and I will be willing to write the code and make it open source. I want to start with creating the architecture and developing API’s for using it. Next I would implement this as a plugin to the different browsers.


  1. User goes to Facebank.com (Some website) which responds with a 401 Unauthorized and sends a nonce in the header.
  2. If certificate mapping exists in the certificate cache, a user will be asked by the browser for which authentication they would like to use. If a certificate doesn’t yet exist at all, the user will be asked to create one by the browser.
    1. This will take the name, address, etc of a user and create a certificate request.
    2. It will send the request to a x509 certificate to an authorizing party and create a valid certificate that is signed by VeriSign or someone like that.
    3. The service will respond with a x509 certificate chain.
    4. The private key of the certificate will be stored in a private key cache. The public certificate will be mapped to the server domain name and stored in the certificate cache.
  3. The certificates private key will be used to encrypt the nonce and send the response back to the server with the public certificate. (This will battle against replay attacks) The server will validate that this is a valid certificate by analyzing the chain. Next the server will decrypt the nonce and ensure that this is the same as the nonce that was originally sent. This validates that the user has the correct private certificate. The server can also check the list of revoked certificates which should be globally available.
  4. Once the certificate is validated, the server can use its internal ways to identify the user and authorize that user to the requested resource. If the user is not already validated, the server can use the information on that certificate to create an account for that user since that certificate will already have the name and address and all the other information.
  5. Next time the user goes to facebank.com, the server will again send a 401 unauthorized header, the browser will simply use the certificate it already has on file, lookup the private key in the private key cache, sign the nonce from the cache, and respond with the public certificate chain and the signed nonce. This will not require user intervention at all.

Certificate Cache

   The certificate cache is a location which stores the public certificates files and the mapping of these certificates to the site domain. This can be build on top of any other certificate store and simply store the mapping of domain name to site. If during login, the server uses HTTPS and presents a valid server certificate, then the certificate cache will map the user certificate to that domain as well as every domain that is on the server certificates subject alternate name. This way if a user goes to facebank.com or http://www.facebank.com, both will use the same certificate and not request additional authentication for both pages.

Private Key Cache

The private key cache is used to store the private key’s of the x509 certificates. This key file will be encrypted using another method. The process of logging in and decrypting the private key file is the equivalent of logging in as a user. When a browser starts for the first time, it will ask what is the current location of the key store. Users can potentially choose to use a local or networked folder (or dropbox), windows key store, or a 3rd party storage. This file will be encrypted using a single password. This password can be set as a string that gets entered once the file is opened, the current windows/user password, or a using a certificate. By using a certificate, this process can be integrated with other PKI systems out there such as ones which use biometrics or key cards. Once this file is decrypted the browser will use it to lookup private keys to sign values which validate the users credentials to the server. The password is never being sent out, but simply proving the ownership of the private key.

Unsupported platforms

If you are using a browser which doesn’t support this functionality, then a HTML authentication method can be created which calls a Javascript API. This API will ask which service is used for private key storage and ask that server to authenticate this user. This can be done using something like OpenAuth. Unfortunately this method wont be supported if the private key cache is stored on a local machine.


If the server wants to be notified when a user logs out then during login, the server will set a cookie with a specific name (auth cookie). When a user clicks logout (or closes the browser), the page will close access to the private key cache, delete the authenticated cookie, and refresh each page which set the cookie to begin with.

Stolen Password

If an x509 private key is stolen, the user will be able to contact the issuing certificate party, which will revoke that certificate. This is why it is important for a server to update its list of revoked certificates. The user will be able to access that site again by going to the lost certificate path.

Lost certificate

The original x509 certificate should be issued to an name/address/email address. Using an email address, a link can be sent which will again give the 401 authentication challenge. The browser will send the new certificate which the server will now store and associate it with the old user information.

Showing impossible to understand Javascript: Obfuscated Quine

What is a Quine? Its a program which returns only its own source code back. In javascript this is very simple to do since converting a function to string will return its source code out. So using that little trick its quite easy to come up with this:

($=function (){return("($="+$+")()" )})()

But that’s too easy. Lets have more fun. We will only use the following characters.

{}()+* <[]$_.-/\",=&';!-

So, as promised, here is the most confusing quine in javascript. Sorry it currently only works on chrome. You can test it in this javascript test bed: http://pastehtml.com/view/b5o84rmsy.html

_=$_=+[],$=+!_,_$=!_+”,_$$=!$+”,__=({})+”,_$_=($/_)+”,__$=__[$+$+$+$+$];$_$=[][__$+__[$]+_$_[$]+__$+_$$[$]+_$[_]];$__=($_$+”)[$$$$=(($+$)+”+(($<<$)*($+$+$)))]+($_$+'')[$$$$-$]+_$$[$]+_$$[$+$];__$$=(__$=$_$+'')[$_++]+__$[$_++]+__$[$_++]+__$[$_++]+__$[$_++]+__$[$_++]+__$[$_++]+__$[$_++];$_$()[_][$__](''+_$$[_]+__[$]+_$[$]+'($$=_,$$$=[];$$<($<<($+$+$));$$++)$$$[$$]=($<<$$)');_$_$=$_$()[_][$__](''+_$$[_]+__[$]+_$[$]+'($$ '+_$_[($<<($+$))+$]+_$_[$]+' $_$()[_])'+_$_[($<<($+$))+$]+_$$[_]+'($$[_]==_$$[$+$+$]&&$$[$]==$__[_]&&$$[$+$]==_$$[$+$+$])$$');$__$=__$$+' (){'+_$[$]+_$[$+$+$]+_$[_]+__$$[$]+_$[$]+_$_[$]+'(';_$__=$_$()[_][$__](''+_$$[_]+__[$]+_$[$]+'($$ '+_$_[($<<($+$))+$]+_$_[$]+' $_$()[_])'+_$_[($<<($+$))+$]+_$$[_]+'($$[_]==_$_$[($<<($+$))*($+$+$)]&&$$[$]==_$[$+$+$]&&$$[$+$]==_$[_]&&$$[$<<($+$)]==__[$])$$');_$$$=$_$()[_][$__](''+_$$[_]+__[$]+_$[$]+'($$ '+_$_[($<<($+$))+$]+_$_[$]+' $_$()[_])'+_$_[($<<($+$))+$]+_$$[_]+'($$[_]==__[$]&&$$[$]==_$_[$]&&$$[$+$]==__[$+$+$+$+$])$$');__$_=$_$()[_][$__](_$_$[($<<($+$+$))-$]+_$[_]+_$[$]+_$_[$+$+$]+_$_[$]+_$_$[($<<($+$))*($+$+$)]+'.'+_$$[_]+_$[$]+__[$]+_$__[($+$+$+$+$)]+_$__[$+$+$]+_$$$[$+$+$]+_$$[$]+_$[$]+_$__[$+$+$]+__[$]+_$__[($+$+$+$+$)*($+$)]+_$[$+$+$]);;$_$()[_][$__]('$_$_='+__$$+'($$,$_$_$,$_$_$_$_){'+_$[$]+_$[$+$+$]+_$[_]+__$$[$]+_$[$]+_$_[$]+' $$.'+_$[$]+_$[$+$+$]+__$_($$$[($+$+$)*($+$)]+$$$[$+$+$+$+$]+$$$[($+$+$+$)])+_$$[$+$]+_$$[$]+_$$$[$+$]+_$[$+$+$]+'($_$_$,$_$_$_$_)'+'}');_$$_=__$_($$$[$+$+$+$+$]+$+$);$_$$=__$_(($<<($+$+$+$))+(($<<($+$))+$+$+$)<<($+$))+_$$_;;___=__$_((($+$+$)+(($+$)*($+$+$+$+$)))*($+$+$));$_$()[_][$__]("($$_="+$__$+"$_$_('_=$_=+[],$=+!_,_$=!_+______,_$$=!$+______,__=({})+______,_$_=($/_)+______,__$=__[$+$+$+$+$];$_$=[][__$+__[$]+_$_[$]+__$+_$$[$]+_$[_]];$__=($_$+______)[$$$$=(($+$)+______+(($<<$)*($+$+$)))]+($_$+______)[$$$$-$]+_$$[$]+_$$[$+$];__$$=(__$=$_$+______)[$_++]+__$[$_++]+__$[$_++]+__$[$_++]+__$[$_++]+__$[$_++]+__$[$_++]+__$[$_++];$_$()[_][$__](______+_$$[_]+__[$]+_$[$]+___($$=_,$$$=[];$$<($<<($+$+$));$$++)$$$[$$]=($<<$$)___);_$_$=$_$()[_][$__](______+_$$[_]+__[$]+_$[$]+___($$ ___+_$_[($<<($+$))+$]+_$_[$]+___ $_$()[_])___+_$_[($<<($+$))+$]+_$$[_]+___($$[_]==_$$[$+$+$]&&$$[$]==$__[_]&&$$[$+$]==_$$[$+$+$])$$___);$__$=__$$+___ (){___+_$[$]+_$[$+$+$]+_$[_]+__$$[$]+_$[$]+_$_[$]+___(___;_$__=$_$()[_][$__](______+_$$[_]+__[$]+_$[$]+___($$ ___+_$_[($<<($+$))+$]+_$_[$]+___ $_$()[_])___+_$_[($<<($+$))+$]+_$$[_]+___($$[_]==_$_$[($<<($+$))*($+$+$)]&&$$[$]==_$[$+$+$]&&$$[$+$]==_$[_]&&$$[$<<($+$)]==__[$])$$___);_$$$=$_$()[_][$__](______+_$$[_]+__[$]+_$[$]+___($$ ___+_$_[($<<($+$))+$]+_$_[$]+___ $_$()[_])___+_$_[($<<($+$))+$]+_$$[_]+___($$[_]==__[$]&&$$[$]==_$_[$]&&$$[$+$]==__[$+$+$+$+$])$$___);__$_=$_$()[_][$__](_$_$[($<<($+$+$))-$]+_$[_]+_$[$]+_$_[$+$+$]+_$_[$]+_$_$[($<<($+$))*($+$+$)]+___.___+_$$[_]+_$[$]+__[$]+_$__[($+$+$+$+$)]+_$__[$+$+$]+_$$$[$+$+$]+_$$[$]+_$[$]+_$__[$+$+$]+__[$]+_$__[($+$+$+$+$)*($+$)]+_$[$+$+$]);;$_$()[_][$__](___$_$_=___+__$$+___($$,$_$_$,$_$_$_$_){___+_$[$]+_$[$+$+$]+_$[_]+__$$[$]+_$[$]+_$_[$]+___ $$.___+_$[$]+_$[$+$+$]+__$_($$$[($+$+$)*($+$)]+$$$[$+$+$+$+$]+$$$[($+$+$+$)])+_$$[$+$]+_$$[$]+_$$$[$+$]+_$[$+$+$]+___($_$_$,$_$_$_$_)___+___}___);_$$_=__$_($$$[$+$+$+$+$]+$+$);$_$$=__$_(($<<($+$+$+$))+(($<<($+$))+$+$+$)<<($+$))+_$$_;',$_$()[_][$__](__$_(''+($<<($+$+$))+($+$),''+$+_+$,''+$+_+($+$+$),''+($+$+$+$+$+$)+($+$+$)*($+$+$),''+$+($+$)+_,''+$+$+($+$))+'('+__$_((($+$+$)+(($+$)*($+$+$+$+$)))*($+$+$))+'___'+__$_((($+$+$)+(($+$)*($+$+$+$+$)))*($+$+$))+','+__$_(''+($+$+$)+(($+$+$)*($+$+$)),''+$+_+($+$+$),''+$+_+($+$+$+$+$),''+($+$+$)+(($+$+$)*($+$+$)))+')'),___)+';___=__$_((($+$+$)+(($+$)*($+$+$+$+$)))*($+$+$));$_$()[_][$__](\"($$_='+$_$_($_$_(($$_+''),_$$_,$_$$),$__$,_$$_+'+$__$+'+_$$_)+')()'+_$$_+')')})()")


Eric Ries posted a competition to create a image of one of his memes and the funnies one gets a book. Original Article here: http://www.startuplessonslearned.com/2011/08/ink-is-on-dead-trees.html. Vote for this one:

Winter is Coming

Usability of passwords response

I just read a article by Thomas Baekdal about how the password “this is fun” is 10 times more secure then “G3a<ag". The article is here http://www.baekdal.com/tips/password-security-usability.
Its a interesting article and I wanted to respond to it, however the response got too long so I instead decided to blog. I had a few thoughts about the detail of putting a 5 second timeout for incorrect passwords. The argument is that even if your password is not too difficult, then any automated script such as one doing a brute force or dictionary attack will take a much longer time to crack it to the extent of thousands of years.

One problem with the 5 second wait, is that a 5 second wait can only be done in a few ways. If you do the wait on the client side, then its completely pointless since every script will bypass that. So to solve that you have to do it on the server side. One way to do that is if the user enters the wrong password, then have the server wait for 5 seconds before returning a "wrong password" result. However the problem with this approach is that a hacker can simply assume that if the page doesn't return in 1 second or so, then the password is incorrect. So a reasonable hacker will start a few password requests at the same time, if the request takes longer then 1 second, simply close the connection and try the next password. This makes it even worse for the server since it now has all of these idle connections which can potentially lead to a denial of service (DOS).

The other approach is, basically what the article is saying, have a lockout period of 1 hour if they do it wrong 10 times in a row. Or in this case the approach is to have a lockout period of 5 seconds every time they do it wrong. What this will do is that if a hacker tries 2 passwords in less then 5 seconds, then the second request should simply be denied even if its on a different server on the farm. This means that all password requests have to be synchronized between all servers on the farm. Additionally this assumes that all servers on the farm have synchronized time to the second. Though this is possible, this isn't thats scalable and has a somewhat high resource utilization.

Another approach I was thinking is that instead of using a 5 second timeout for incorrect password requests, do a random timeout for both incorrect or correct passwords. So if a user enters a incorrect password, pick a random number from 0 to 5 and have the server wait that long before returning a result. Sometimes, the incorrect password request will come back right away, sometimes it wont come back for a few seconds. However what this gives is the hacker the uncertainty of the incorrect password being a function of a "greater then X second(s) wait". The downside of this approach is that unfortunately you still have to do a random timeout for users which enter the password correctly. If you don't do this wait, then a hacker can simply still assume that a wait longer then X second(s) is an incorrect password. Unfortunately this is also heavy on the server since this increases server load for TCP connections and can potentially cause a DOS.

I think the best approach of all is simply not to do this but have some sort of a lockout after a number of incorrect password requests. This would be a global lockout which will force the user to have to reset their password by sending a email or performing some sort of verification (difficult CAPTCHA maybe?). (this will require the hacker to go and hack the email as well and essentially bother someone else's server). Also if the lockout is indefinite until some sort of verification than the time it takes to brute force a password becomes exponentially greater.


Since everyone is talking about it, I had to create my own post to talk about Color. When I tried it, I was unfortunately unable to get past the first couple of screens and it crashed every time I pressed anything. However this comes with the territory of “Beta”. But the big news has nothing to do with the application but the fact that they were given 41 million for hitting every buzzword “mobile, local, social” in their evaluation.

After reading this TC article and watching the video on it I am concerned. http://techcrunch.com/2011/03/23/color-looks-to-reinvent-social-interaction-with-its-mobile-photo-app-and-41-million-in-funding/ Maybe I am misunderstanding the idea, it’s a place that you can see photos that people take within 150 foot radius and they will stay with you as part of your network. So if I live in an apartment and someone next door wants to use this application to take a picture of their inappropriate parts, this will be part of my picture stream? So in the future if I want to show my grandmother pictures of my birthday party, she will have to look at my 40 year old neighbor’s junk? To me the idea of a social graph is one which I decide on, not one which is decided for me based on location. I agree that most of the time this will be based on the people that are always around me, however my pictures are a somewhat intimate part which I would want to control access to. Additionally, if my girlfriend takes a picture of herself to give to me, I wouldn’t want that same 40 year old neighbor looking at it. Color.com privacy policy even states “Before you use our App, consider whether you (or those whose image you capture) want the world to see the picture or video you took.” There is no protection however from seeing what you don’t want to see, and not having this part of your album and memories forever. For example a spammer can take a picture of a Viagra ad, then drive around the city to make sure everyone has this. Because the content is automatically made of everything around you, everyone will have this Viagra ad forever.

I hope Bain, Sequoia, and Silicon Valley Bank don’t lose that 41 million for nothing!

Cultured View Engine for MVC

Since we are running on .NET and MVC2, I would expect that by now there would be a way to use globalization within the application. Unfortunately, after looking around I have found that by default the only way this is possible is through resource files. Resource files are great for some things such as master pages where there is not a lot of formatted data, but simply a few replaced values. Additionally, they are required for some things where there is a lot of custom Javascript or code logic which would be impossible to update all of the pages once a single bug is fixed. However for a page with lots of content (such as a terms and conditions page) it is impossible to store all of that data in formatted a resource file. To complete this we took a mixed approach. The pages which have a lot of custom code and don’t have a lot of text, we will store this information in a resource file. For pages with more text then code, we use a view for each page. Everything in the middle is a combination of a custom view with a shared partial view.

We wanted to be able to plug-in on top of the current structure and simply provide more functionality. Meaning, that including this view engine would not break the old code or structure that already exists. We found that the easiest approach is to simply use the current ASP.NET form view engine, but simply modify its searching technique. Additionally being from the US, we decided to hard code “en-US” as the default culture for all pages. This allows us to create certain administrative pages which will never be seen by the end customer and not have to worry about creating different cultures for those.

The first approach is to decide on the structure or layout of code. We wanted to keep the “Shared” folder in the same place, but simply add cultures into each folder. For example the following is the folder structure we wanted to maintain for the different views

  • ~/Views/Shared/Site.master – This is multilingual and shared between all places.
  • ~/Views/en-US/Home/Index.aspx – This is the US English version of the home page.
  • ~/Views/pt-BR/Home/Index.aspx – This is the Brazilian Portuguese version of the home page.

The default ASP.NET WebFormView engine only would look at the pages in the following locations:

  • ~/Views/{Controller}/{Page}.aspx
  • ~/Views/{Controller}/{Page}.ascx
  • ~/Views/Shared/{Page}.aspx
  • ~/Views/Shared/{Page}.ascx

So the first thing we had to do was allow the view to search more locations. We also used the information of whether to display full view or a partial view select between “aspx” and “ascx” file extensions. Also since we didn’t want to have to create our own rendering and do a lot of work, we simply enherit from the System.Web.Mvc.WebFormViewEngine.

public class LocalizedViewEngine : WebFormViewEngine
   public LocalizedViewEngine()
      this.ViewLocationFormats = new string[]
         //{0} - Culture Name, {1} - Controller, {2} - Page, {3} Extension (aspx/ascx)

Now using this format we can actually change the order of the parameters and be able to change the folder structure. For example instead of using the format we decided of Culture/Controller/Name.extension, you can use your own.

Format Example View Location Format
~/Views/Culture/Controller/Page.Extension ~/Views/en-US/Home/Index.aspx ~/Views/{0}/{1}/{2}{3}
~/Views/Controller/Culture/Page.Extension ~/Views/Home/en-US/Index.aspx ~/Views/{1}/{0}/{2}{3}
~/Views/Controller/Page.Culture.Extension ~/Views/Home/Index.en-US.aspx ~/Views/{1}/{2}.{0}{3}
~/Culture/Views/Controller/Page.Extension ~/en-US/Views/Home/Index.aspx ~/{0}/Views/{1}/{2}{3}

After selecting the paths you would like to use, simply place that location in either the constructor or set that property in the global.asax file when initializing the view engine.

The next part consists of actually looking for the paths and finding the appropriate one. Our approach looks at the current UI culture, however if you would like to pass that information in the url, I will talk about that in the end.

This function we added to the engine which will do the actual work of finding the path:

private ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache, string extension)
   //Get the current culture and name of it.
   var culture = CultureInfo.CurrentUICulture;
   var cultureName = culture != null ? culture.Name : string.Empty;

   //Get the name of the controller from the path
   var controller = controllerContext.RouteData.GetRequiredString("controller");                        

   //Create the key to use the culture for caching purposes            
   var keyPath = Path.Combine(cultureName, controller, viewName + extension);

   //Try the cache            
   if (useCache)            
      //If using the cache, check to see if the location is cached.                
      var cacheLocation = this.ViewLocationCache.GetViewLocation(controllerContext.HttpContext, keyPath);
      if (!string.IsNullOrWhiteSpace(cacheLocation))
         return new ViewEngineResult(CreateView(controllerContext, cacheLocation, masterName), this);

   //Remember the attempted paths, if not found display the attempted paths in the error message.            
   var attempts = new List<string>();

   //for each of the paths defined, format the string and see if that path exists. When found, cache it.            
   foreach (var rootPath in this.ViewLocationFormats)            
      var currentPath = string.Format(rootPath, cultureName, controller, viewName, extension);
      if (this.FileExists(controllerContext, currentPath))                
         this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, keyPath, currentPath);                    
         return new ViewEngineResult(CreateView(controllerContext, currentPath, masterName), this);                
      //If not found, add to the list of attempts.                

   //if not found by now, simply return the attempted paths.            
   return new ViewEngineResult(attempts);        

Now the last part is simple, to override the two functions which will actually do the work of finding and creating the view engine.

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)        
   return FindView(controllerContext, viewName, masterName, useCache, ".aspx");        

public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)        
   return FindView(controllerContext, partialViewName, string.Empty, useCache, ".ascx");        

This is the entire class. Now to actually use this class simply organize your folder structure in the way that you selected and add the following two lines to the Application_Start() method of the Global.asax

ViewEngines.Engines.Add(new LocalizedViewEngine());

Specifying the culture in the URL

In some cases, you might want to be able to specify the culture in the URL. This allows a user to simply change the culture by changing the URL without your code having to do anything. For example, instead of going to
and seeing the culture in your location, you can go to
to see the english version, and
to see the Portuguese version.  MVC luckly will help us out with this using the same view engine. You will have to make a replace two lines in your ViewEngine and add the culture column into your Routes table in the global.asax file.

First to add the culture information into the route, add the {culture} field into the default route. It should look something like this:

              "Default", // Route name
              "{culture}/{controller}/{action}/{id}", // URL with parameters
              new { culture="en-US", controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults            

This will set the “en-US” culture as the default.

Next, in the view engine, replace the following to lines:

var culture = CultureInfo.CurrentUICulture;
var cultureName = culture != null ? culture.Name : string.Empty;

With the following single line:

var cultureName = controllerContext.RouteData.GetRequiredString("culture");

This will get the culture name from the route information.

Be careful with this approach however, the user or browser is able to enter invalid routes which might get unexpected results. For example when a browser goes to a page, the browser always tries to get the icon for the page by going to favicon.ico. This will be thought of as the culture information. For this reason, you must add extra routes to take care of such exceptions.

This code is provided as is, and hopefully it can be of some use to you.


Ok I created a sample solution and uploaded it here:

Localized View Engine Sample.zip

Here are a few screen shots:

Brand New World

Creating a service or application is not what it used to be. Besides having to create the actual idea, formalize it, gather feedback, get subscribers, and about 1000 different things, you also have to tell people about what you are doing and how. The more transparency, the better your service looks. The more fluid the communication is with your subscribers the better the service can become. For this reason, from the very beginning we are going to dedicate ourselves to our subscribers. What you want is what you will get!

What is OiMae?

OiMae is a service dedicated to allowing people from around the world communicate, breaking language barriers that might have existed before. OiMae allows users to communicate with people around the world in their native language. Just click on a friend, and start talking in your native language. Next time you want to schedule a gondala ride in Venice, ask for the best table in France, or simply keep up with your in-laws in Brazil, you can do this by going to OiMae.com.

How did it start?

Have you ever traveled to another country?  I have. I went to meet my girlfriends parents in Brazil. The only problem is I don’t speak Portuguese. Luckily she was there to translate for me (besides when she got mad and didn’t want to help). After this vacation, I realized that I had 2 choices; learn Portuguese, or hack my way to speaking. I knew that learning Portuguese was going to take too long and be very difficult. So instead I started OiMae. This way I can talk to people in Brazil without having to learn their language. The one phrase I did however learn was “oi mãe” meaning “hi mom” in Portuguese.

I realized that this could really help the world. By allowing everyday people to communicate freely, there are less barriers in the world. Less barriers can educate each of us about the other cultures. This over time can even lead to world peace. Does this mean that we can create a brand new world?


Get every new post delivered to your Inbox.